[FEATURE] Typolink ViewHelper 17/30617/13
authorAnja Leichsenring <aleichsenring@ab-softlab.de>
Fri, 6 Jun 2014 20:07:59 +0000 (22:07 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Sun, 9 Nov 2014 13:31:08 +0000 (14:31 +0100)
Add a view helper that parses the good old typolink string
syntax created by link wizards and rte.

Change-Id: Ie220b3f8df41e79206603bec034ae36674d98594
Resolves: #59396
Releases: master
Reviewed-on: http://review.typo3.org/30617
Reviewed-by: Stefan Froemken <froemken@gmail.com>
Tested-by: Stefan Froemken <froemken@gmail.com>
Reviewed-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
Tested-by: Jigal van Hemert <jigal.van.hemert@typo3.org>
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-59396-TypolinkViewHelper.rst [new file with mode: 0644]
typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php [new file with mode: 0644]
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php [new file with mode: 0644]

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-59396-TypolinkViewHelper.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-59396-TypolinkViewHelper.rst
new file mode 100644 (file)
index 0000000..5e88917
--- /dev/null
@@ -0,0 +1,45 @@
+====================================
+Feature: #59396 - TypolinkViewHelper
+====================================
+
+Description
+===========
+
+Adding a ViewHelper that copes with the contents of any field that was filled with a link wizard in
+TYPO3 CMS Backend.
+Those fields contain various parts split by a space and being escaped to provide input for the
+typoLink function.
+In order to use those fields natively in Fluid without the need of TypoScript in between, this ViewHelper
+was introduced.
+It takes the field content as a whole and can additionally take some parameters directly from Fluid.
+
+The full parameter usage in Fluid might look like this, where {link} is the field content:
+
+::
+
+<f:link.typolink parameter="{link}" target="_blank" class="ico-class" title="some title" additionalParams="" additionalAttributes="{type:'button'}">
+
+..
+
+Only *parameter* is required, all other parameters are optional.
+While passing additional parameters to the ViewHelper, following rules apply:
+
+- target is overridden, the value from Fluid applies
+- class is merged from the values passed from the database and those of *class*
+- title is overridden, the value from Fluid applies
+- additionalParams is merged from the values passed from the database and those of *additionalParams*
+- additionalAttributes is (as usual) added to the resulting tag as *type="button"*
+
+{link} contains *19 _blank - "testtitle with whitespace" &X=y*.
+For the given example, the output is:
+
+::
+
+<a href="index.php?id=19&X=y" title="some title" target="_blank" class="ico-class" type="button">
+
+..
+
+Impact
+======
+
+The new ViewHelper can be used in all new projects. There is no interference with any part of existing code.
\ No newline at end of file
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php
new file mode 100644 (file)
index 0000000..74fe986
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+namespace TYPO3\CMS\Fluid\ViewHelpers\Link;
+
+/*                                                                        *
+ * This script is part of the TYPO3 project - inspiring people to share!  *
+ *                                                                        *
+ * TYPO3 is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License version 2 as published by  *
+ * the Free Software Foundation.                                          *
+ *                                                                        *
+ * This script is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General      *
+ * Public License for more details.                                       *
+ *                                                                        */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+
+/**
+ * A ViewHelper to create links from fields supported by the link wizard
+ *
+ * == Example ==
+ *
+ * {link} contains "19 _blank - "testtitle with whitespace" &X=y"
+ *
+ * <code title="minimal usage">
+ * <f:link.typolink parameter="{link}">
+ * Linktext
+ * </f:link.typolink>
+ * <output>
+ * <a href="index.php?id=19&X=y" title="testtitle with whitespace" target="_blank">
+ * Linktext
+ * </a>
+ * </output>
+ * </code>
+ *
+ * <code title="Full parameter usage">
+ * <f:link.typolink parameter="{link}" target="_blank" class="ico-class" title="some title" additionalParams="&u=b" additionalAttributes="{type:'button'}">
+ * Linktext
+ * </f:link.typolink>
+ * </code>
+ * <output>
+ * <a href="index.php?id=19&X=y&u=b" title="some title" target="_blank" class="ico-class" type="button">
+ * Linktext
+ * </a>
+ * </output>
+ *
+ */
+class TypolinkViewHelper extends AbstractViewHelper {
+
+       /**
+        * Render
+        *
+        * @param string $parameter stdWrap.typolink style parameter string
+        * @param string $target
+        * @param string $class
+        * @param string $title
+        * @param string $additionalParams
+        * @param array $additionalAttributes
+        *
+        * @return string
+        */
+       public function render($parameter, $target = '', $class = '', $title = '', $additionalParams = '', $additionalAttributes = array()) {
+               // Merge the $parameter with other arguments
+               $typolinkParameter = $this->createTypolinkParameterArrayFromArguments($parameter, $target, $class, $title, $additionalParams);
+
+               // array(param1 -> value1, param2 -> value2) --> "param1=value1 param2=>value2" for typolink.ATagParams
+               $extraAttributes = array();
+               foreach ($additionalAttributes as $attributeName => $attributeValue) {
+                       $extraAttributes[] = $attributeName . '="' . htmlspecialchars($attributeValue) . '"';
+               }
+               $aTagParams = implode(' ', $extraAttributes);
+
+               // If no link has to be rendered, the inner content will be returned as such
+               $content = $this->renderChildren();
+
+               if ($parameter) {
+                       /** @var ContentObjectRenderer $contentObject */
+                       $contentObject = GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer');
+                       $contentObject->start(array(), '');
+                       $content = $contentObject->stdWrap(
+                               $content,
+                               array(
+                                       'typolink.' => array(
+                                               'parameter' => implode(' ', $typolinkParameter),
+                                               'ATagParams' => $aTagParams,
+                                       )
+                               )
+                       );
+               }
+
+               return $content;
+       }
+
+       /**
+        * Transforms ViewHelper arguments to typo3link.parameters.typoscript option as array.
+        *
+        * @param string $parameter Example: 19 _blank - "testtitle with whitespace" &X=y
+        * @param string $target
+        * @param string $class
+        * @param string $title
+        * @param string $additionalParams
+        *
+        * @return array Final merged typolink.parameter as array to be imploded with empty string later
+        */
+       protected function createTypolinkParameterArrayFromArguments($parameter, $target = '', $class = '', $title = '', $additionalParams = '') {
+               // Explode $parameter by whitespace and remove any " around resulting array values
+               $parameterArray = GeneralUtility::unQuoteFilenames($parameter, TRUE);
+
+               if (empty($parameterArray)) {
+                       return array();
+               }
+
+               // Extend to 4 elements
+               $typolinkConfiguration = array_pad($parameterArray, 4, '-');
+
+               // Override target if given in target argument
+               if ($target) {
+                       $typolinkConfiguration[1] = $target;
+               }
+
+               // Combine classes if given in both "parameter" string and "class" argument
+               if ($class) {
+                       $typolinkConfiguration[2] = $typolinkConfiguration[2] !== '-' ? $typolinkConfiguration[2] . ' ' : '';
+                       $typolinkConfiguration[2] .= $class;
+               }
+
+               // Override title if given in title argument
+               if ($title) {
+                       $typolinkConfiguration[3] = $title;
+               }
+
+               // Combine additionalParams
+               if ($additionalParams) {
+                       $typolinkConfiguration[4] .= $additionalParams;
+               }
+
+               // Unset unused parameters again from the end, wrap all given values with "
+               $reverseSortedParameters = array_reverse($typolinkConfiguration, TRUE);
+               $aValueWasSet = FALSE;
+               foreach ($reverseSortedParameters as $position => $value) {
+                       if ($value === '-' && !$aValueWasSet) {
+                               unset($typolinkConfiguration[$position]);
+                       } else {
+                               $aValueWasSet = TRUE;
+                               if ($value !== '-') {
+                                       $typolinkConfiguration[$position] = '"' . $value . '"';
+                               }
+                       }
+               }
+
+               return $typolinkConfiguration;
+       }
+}
\ No newline at end of file
diff --git a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php
new file mode 100644 (file)
index 0000000..b9f67d9
--- /dev/null
@@ -0,0 +1,255 @@
+<?php
+namespace TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\Link;
+
+/*                                                                        *
+ * This script is part of the TYPO3 project - inspiring people to share!  *
+ *                                                                        *
+ * TYPO3 is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License version 2 as published by  *
+ * the Free Software Foundation.                                          *
+ *                                                                        *
+ * This script is distributed in the hope that it will be useful, but     *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
+ * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General      *
+ * Public License for more details.                                       *
+ *                                                                        */
+
+use TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\ViewHelperBaseTestcase;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Class TypolinkViewHelperTest
+ */
+class TypolinkViewHelperTest extends ViewHelperBaseTestcase {
+
+       /**
+        * @test
+        */
+       public function renderCallsRenderChildrenOnce() {
+               /** @var \TYPO3\CMS\Fluid\ViewHelpers\Link\TypolinkViewHelper|\PHPUnit_Framework_MockObject_MockObject $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Fluid\\ViewHelpers\\Link\\TypolinkViewHelper', array('renderChildren'));
+               $subject->expects($this->once())->method('renderChildren');
+               $subject->render('');
+       }
+
+       /**
+        * @test
+        */
+       public function renderReturnsContentOfRenderChildrenIfNoLinkParameterIsGiven() {
+               /** @var \TYPO3\CMS\Fluid\ViewHelpers\Link\TypolinkViewHelper|\PHPUnit_Framework_MockObject_MockObject $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Fluid\\ViewHelpers\\Link\\TypolinkViewHelper', array('renderChildren'));
+               $subject->expects($this->any())->method('renderChildren')->will($this->returnValue('innerContent'));
+               $this->assertEquals('innerContent', $subject->render(''));
+       }
+
+       /**
+        * @test
+        */
+       public function renderGivesMergedParametersToContentObjectRenderer() {
+               /** @var \TYPO3\CMS\Fluid\ViewHelpers\Link\TypolinkViewHelper|\PHPUnit_Framework_MockObject_MockObject $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Fluid\\ViewHelpers\\Link\\TypolinkViewHelper', array('renderChildren'));
+               $subject->expects($this->any())->method('renderChildren')->will($this->returnValue('innerContent'));
+               $contentObjectRendererMock = $this->getMock('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer', array(), array(), '', FALSE);
+
+               $params = '19 _blank css-class "testtitle with whitespace" &X=y';
+               $target = '-';
+               $class = 'fluid_class';
+               $title = 'a new title';
+               $additionalParams = '&a=b';
+               $additionalAttributes = array(
+                       'value1' => 'param1',
+                       'value2' => 'par&am2', // Check htmlspecialchars is applied
+               );
+
+               $expectedParametersToStdWrap = array(
+                       'typolink.' => array(
+                               'parameter' => '"19" - "css-class fluid_class" "a new title" "&X=y&a=b"',
+                               'ATagParams' => 'value1="param1" value2="par&amp;am2"',
+                       ),
+               );
+
+               $contentObjectRendererMock->expects($this->once())->method('stdWrap')->with('innerContent', $expectedParametersToStdWrap);
+
+               GeneralUtility::addInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer', $contentObjectRendererMock);
+
+               $subject->render($params, $target, $class, $title, $additionalParams, $additionalAttributes);
+       }
+
+       /**
+        * @test
+        */
+       public function renderReturnsResultOfContentObjectRenderer() {
+               /** @var \TYPO3\CMS\Fluid\ViewHelpers\Link\TypolinkViewHelper|\PHPUnit_Framework_MockObject_MockObject $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Fluid\\ViewHelpers\\Link\\TypolinkViewHelper', array('renderChildren'));
+               $subject->expects($this->any())->method('renderChildren')->will($this->returnValue('innerContent'));
+               $contentObjectRendererMock = $this->getMock('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer', array(), array(), '', FALSE);
+               $contentObjectRendererMock->expects($this->once())->method('stdWrap')->will($this->returnValue('foo'));
+               GeneralUtility::addInstance('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer', $contentObjectRendererMock);
+               $this->assertEquals('foo', $subject->render('42'));
+       }
+
+       /**
+        * @return array
+        */
+       public function typoScriptConfigurationData() {
+               return array(
+                       'empty input' => array(
+                               '', // input from link field
+                               '', // target from fluid
+                               '', // class from fluid
+                               '', // title from fluid
+                               '', // additional parameters from fluid
+                               array(),
+                       ),
+                       'simple id input' => array(
+                               19,
+                               '',
+                               '',
+                               '',
+                               '',
+                               array(
+                                       0 => '"19"',
+                               ),
+                       ),
+                       'external url with target' => array(
+                               'www.web.de _blank',
+                               '',
+                               '',
+                               '',
+                               '',
+                               array(
+                                       0 => '"www.web.de"',
+                                       1 => '"_blank"',
+                               ),
+                       ),
+                       'page with class' => array(
+                               '42 - css-class',
+                               '',
+                               '',
+                               '',
+                               '',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '"css-class"',
+                               ),
+                       ),
+                       'page with extended class' => array(
+                               '42 - css-class',
+                               '',
+                               'fluid_class',
+                               '',
+                               '',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '"css-class fluid_class"',
+                               ),
+                       ),
+                       'page with title' => array(
+                               '42 - - "a link title"',
+                               '',
+                               '',
+                               '',
+                               '',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '-',
+                                       3 => '"a link title"'
+                               )
+                       ),
+                       'page with overridden title' => array(
+                               '42 - - "a link title"',
+                               '',
+                               '',
+                               'another link title',
+                               '',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '-',
+                                       3 => '"another link title"',
+                               ),
+                       ),
+                       'page with title and parameters' => array(
+                               '42 - - "a link title" &x=y',
+                               '',
+                               '',
+                               '',
+                               '',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '-',
+                                       3 => '"a link title"',
+                                       4 => '"&x=y"',
+                               ),
+                       ),
+                       'page with title and extended parameters' => array(
+                               '42 - - "a link title" &x=y',
+                               '',
+                               '',
+                               '',
+                               '&a=b',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '-',
+                                       3 => '"a link title"',
+                                       4 => '"&x=y&a=b"',
+                               ),
+                       ),
+                       'full parameter usage' => array(
+                               '19 _blank css-class "testtitle with whitespace" &X=y',
+                               '-',
+                               'fluid_class',
+                               'a new title',
+                               '&a=b',
+                               array(
+                                       0 => '"19"',
+                                       1 => '-',
+                                       2 => '"css-class fluid_class"',
+                                       3 => '"a new title"',
+                                       4 => '"&X=y&a=b"',
+                               ),
+                       ),
+                       'only page id and overwrite' => array(
+                               '42',
+                               '',
+                               '',
+                               '',
+                               '&a=b',
+                               array(
+                                       0 => '"42"',
+                                       1 => '-',
+                                       2 => '-',
+                                       3 => '-',
+                                       4 => '"&a=b"',
+                               ),
+                       ),
+                       'email' => array(
+                               'a@b.tld',
+                               '',
+                               '',
+                               '',
+                               '',
+                               array(
+                                       '"a@b.tld"',
+                               ),
+                       ),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider typoScriptConfigurationData
+        */
+       public function createTypolinkParameterArrayFromArgumentsReturnsExpectedArray($input, $targetFromFluid, $classFromFluid, $titleFromFluid, $additionalParametersFromFluid, $expected) {
+               /** @var \TYPO3\CMS\Fluid\ViewHelpers\Link\TypolinkViewHelper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
+               $subject = $this->getAccessibleMock('TYPO3\\CMS\\Fluid\\ViewHelpers\\Link\\TypolinkViewHelper', array('dummy'));
+               $result = $subject->_call('createTypolinkParameterArrayFromArguments', $input, $targetFromFluid, $classFromFluid, $titleFromFluid, $additionalParametersFromFluid);
+               $this->assertSame($expected, $result);
+       }
+
+}