[FEATURE] Add TypoLinkCodec 73/40673/6
authorMarkus Klein <markus.klein@typo3.org>
Fri, 26 Jun 2015 12:00:04 +0000 (14:00 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Sat, 27 Jun 2015 14:50:43 +0000 (16:50 +0200)
The class allows to encode or decode TypoLink strings.

Resolves: #67765
Releases: master
Change-Id: Ibe103ee5c60e411e4d9d965c3623d80f20cc6435
Reviewed-on: http://review.typo3.org/40673
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
typo3/sysext/core/Documentation/Changelog/master/Feature-67765-IntroduceTypoLinkCodecService.rst [new file with mode: 0644]
typo3/sysext/frontend/Classes/Service/TypoLinkCodecService.php [new file with mode: 0644]
typo3/sysext/frontend/Tests/Unit/Service/TypoLinkCodecServiceTest.php [new file with mode: 0644]

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-67765-IntroduceTypoLinkCodecService.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-67765-IntroduceTypoLinkCodecService.rst
new file mode 100644 (file)
index 0000000..a163f69
--- /dev/null
@@ -0,0 +1,13 @@
+================================================
+Feature: #67765 - Introduce TypoLinkCodecService
+================================================
+
+Description
+===========
+
+The new ``TypoLinkCodecService`` class helps to simplify encoding and decoding of TypoLink strings.
+
+A given TypoLink string can be passed to the ``decode`` method, which will return an associative array with the decoded parts.
+The ``encode`` method takes care of assembling a valid TypoLink string for an array of TypoLink parts.
+
+The encoding uses proper quoting and escaping, which allows safe usage of characters like ``"\<space>``.
diff --git a/typo3/sysext/frontend/Classes/Service/TypoLinkCodecService.php b/typo3/sysext/frontend/Classes/Service/TypoLinkCodecService.php
new file mode 100644 (file)
index 0000000..fec33d8
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+namespace TYPO3\CMS\Frontend\Service;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This class provides basic functionality to encode and decode typolink strings
+ *
+ * @author Markus Klein <markus.klein@typo3.org>
+ */
+class TypoLinkCodecService {
+
+       /**
+        * Delimiter for TypoLink string parts
+        *
+        * @var string
+        */
+       static protected $partDelimiter = ' ';
+
+       /**
+        * Symbol for TypoLink parts not specified
+        *
+        * @var string
+        */
+       static protected $emptyValueSymbol = '-';
+
+       /**
+        * Encode TypoLink parts to a single string
+        *
+        * @param array $typoLinkParts Array with keys url and optionally any of target, class, title, additionalParams
+        * @return string Returns a correctly encoded TypoLink string
+        */
+       public function encode(array $typoLinkParts) {
+               if (empty($typoLinkParts) || !isset($typoLinkParts['url'])) {
+                       return '';
+               }
+
+               // Get empty structure
+               $reverseSortedParameters = array_reverse($this->decode(''), TRUE);
+               $aValueWasSet = FALSE;
+               foreach ($reverseSortedParameters as $key => &$value) {
+                       $value = isset($typoLinkParts[$key]) ? $typoLinkParts[$key] : '';
+                       // escape special character \ and "
+                       $value = str_replace([ '\\', '"' ], [ '\\\\', '\\"' ], $value);
+                       // enclose with quotes if a string contains the delimiter
+                       if (strpos($value, static::$partDelimiter) !== FALSE) {
+                               $value = '"' . $value . '"';
+                       }
+                       // fill with - if another values has already been set
+                       if ($value === '' && $aValueWasSet) {
+                               $value = static::$emptyValueSymbol;
+                       }
+                       if ($value !== '') {
+                               $aValueWasSet = TRUE;
+                       }
+               }
+
+               return trim(implode(static::$partDelimiter, array_reverse($reverseSortedParameters, TRUE)));
+       }
+
+       /**
+        * Decodes a TypoLink string into its parts
+        *
+        * @param string $typoLink The properly encoded TypoLink string
+        * @return array Associative array of TypoLink parts with the keys url, target, class, title, additionalParams
+        */
+       public function decode($typoLink) {
+               $typoLink = trim($typoLink);
+               if ($typoLink !== '') {
+                       $parts = str_replace([ '\\\\', '\\"' ], [ '\\', '"' ], str_getcsv($typoLink, static::$partDelimiter));
+               } else {
+                       $parts = '';
+               }
+
+               // The order of the entries is crucial!!
+               $typoLinkParts = [
+                       'url' => isset($parts[0]) ? trim($parts[0]) : '',
+                       'target' => isset($parts[1]) && $parts[1] !== static::$emptyValueSymbol ? trim($parts[1]) : '',
+                       'class' => isset($parts[2]) && $parts[2] !== static::$emptyValueSymbol ? trim($parts[2]) : '',
+                       'title' => isset($parts[3]) && $parts[3] !== static::$emptyValueSymbol ? trim($parts[3]) : '',
+                       'additionalParams' => isset($parts[4]) && $parts[4] !== static::$emptyValueSymbol ? trim($parts[4]) : ''
+               ];
+
+               return $typoLinkParts;
+       }
+
+}
diff --git a/typo3/sysext/frontend/Tests/Unit/Service/TypoLinkCodecServiceTest.php b/typo3/sysext/frontend/Tests/Unit/Service/TypoLinkCodecServiceTest.php
new file mode 100644 (file)
index 0000000..deabc37
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+namespace TYPO3\CMS\Frontend\Tests\Unit\Service;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
+
+/**
+ * Test case
+ */
+class TypoLinkCodecServiceTest extends UnitTestCase {
+
+       /**
+        * @var TypoLinkCodecService
+        */
+       protected $subject;
+
+       /**
+        * Set up test subject
+        */
+       protected function setUp() {
+               $this->subject = new TypoLinkCodecService();
+       }
+
+       /**
+        * @test
+        * @dataProvider encodeReturnsExpectedResultDataProvider
+        * @param array $parts
+        * @param string$expected
+        */
+       public function encodeReturnsExpectedResult(array $parts, $expected) {
+               $this->assertSame($expected, $this->subject->encode($parts));
+       }
+
+       /**
+        * @return array
+        */
+       public function encodeReturnsExpectedResultDataProvider() {
+               return [
+                       'empty input' => [
+                               [
+                                       'url' => '',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => '',
+                                       'additionalParams' => ''
+                               ],
+                               ''
+                       ],
+                       'full parameter usage' => [
+                               [
+                                       'url' => '19',
+                                       'target' => '_blank',
+                                       'class' => 'css-class',
+                                       'title' => 'testtitle with whitespace',
+                                       'additionalParams' => '&x=y'
+                               ],
+                               '19 _blank css-class "testtitle with whitespace" &x=y'
+                       ],
+                       'crazy title and partial items only' => [
+                               [
+                                       'url' => 'foo',
+                                       'title' => 'a "link\\ ti\\"tle',
+                               ],
+                               'foo - - "a \\"link\\\\ ti\\\\\\"tle"'
+                       ]
+               ];
+       }
+
+       /**
+        * @test
+        * @dataProvider decodeReturnsExpectedResultDataProvider
+        * @param string $typoLink
+        * @param array $expected
+        */
+       public function decodeReturnsExpectedResult($typoLink, array $expected) {
+               $this->assertSame($expected, $this->subject->decode($typoLink));
+       }
+
+       /**
+        * @return array
+        */
+       public function decodeReturnsExpectedResultDataProvider() {
+               return [
+                       'empty input' => [
+                               '',
+                               [
+                                       'url' => '',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => '',
+                                       'additionalParams' => ''
+                               ],
+                       ],
+                       'simple id input' => [
+                               '19',
+                               [
+                                       'url' => '19',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => '',
+                                       'additionalParams' => ''
+                               ],
+                       ],
+                       'external url with target' => [
+                               'www.web.de _blank',
+                               [
+                                       'url' => 'www.web.de',
+                                       'target' => '_blank',
+                                       'class' => '',
+                                       'title' => '',
+                                       'additionalParams' => ''
+                               ],
+                       ],
+                       'page with class' => [
+                               '42 - css-class',
+                               [
+                                       'url' => '42',
+                                       'target' => '',
+                                       'class' => 'css-class',
+                                       'title' => '',
+                                       'additionalParams' => ''
+                               ],
+                       ],
+                       'page with title' => [
+                               '42 - - "a link title"',
+                               [
+                                       'url' => '42',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => 'a link title',
+                                       'additionalParams' => ''
+                               ],
+                       ],
+                       'page with crazy title' => [
+                               '42 - - "a \\"link\\\\ ti\\\\\\"tle"',
+                               [
+                                       'url' => '42',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => 'a "link\\ ti\\"tle',
+                                       'additionalParams' => ''
+                               ],
+                       ],
+                       'page with title and parameters' => [
+                               '42 - - "a link title" &x=y',
+                               [
+                                       'url' => '42',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => 'a link title',
+                                       'additionalParams' => '&x=y'
+                               ],
+                       ],
+                       'page with complex title' => [
+                               '42 - - "a \\"link\\" title with \\\\" &x=y',
+                               [
+                                       'url' => '42',
+                                       'target' => '',
+                                       'class' => '',
+                                       'title' => 'a "link" title with \\',
+                                       'additionalParams' => '&x=y'
+                               ],
+                       ],
+                       'full parameter usage' => [
+                               '19 _blank css-class "testtitle with whitespace" &X=y',
+                               [
+                                       'url' => '19',
+                                       'target' => '_blank',
+                                       'class' => 'css-class',
+                                       'title' => 'testtitle with whitespace',
+                                       'additionalParams' => '&X=y'
+                               ],
+                       ],
+               ];
+       }
+
+}