diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-90026-ExposeInternalTypoLinkPartsInTypolinkViewHelper.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-90026-ExposeInternalTypoLinkPartsInTypolinkViewHelper.rst new file mode 100644 index 0000000000000000000000000000000000000000..f3bba8b751ecf5087d03796f5ff56635dbafea7f --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-90026-ExposeInternalTypoLinkPartsInTypolinkViewHelper.rst @@ -0,0 +1,46 @@ +.. include:: ../../Includes.txt + +===================================================================== +Feature: #90026 - Expose internal typoLinkParts in TypolinkViewHelper +===================================================================== + +See :issue:`90026` + +Description +=========== + +Parameters being generated internally by TypoLink using +:html:`<f:link.typolink parts-as="typoLinkParts">` view helper are exposed as +variable and can be used in Fluid templates again. + +View helper attribute `parts-as` (default `typoLinkParts`) allows to define +variable name to be used containing the following internal parts: + +* url +* target +* class +* title +* additionalParams + +Details for these internal parts are documented for :ts:`typolink.parameter` +in `TypoScript reference`_ + +.. _TypoScript reference: https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html?highlight=typolink#parameter + +Impact +====== + +Multiple instructions for attribute `parameter` (e.g. persisted to entity +record) can be used individually. + +.. code-block: html + + <f:link.typolink parameter="123 _top news title" parts-as="parts"> + {parts.url} + {parts.target} + {parts.class} + {parts.title} + {parts.additionalParams} + </f:link.typolink> + +.. index:: Fluid, Frontend, ext:fluid diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php index ab2a47a0867f165b232bcddbd99be445e5bd3ae2..125e450f1744ce49d248225baee106c9c8c3e7be 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Link/TypolinkViewHelper.php @@ -100,6 +100,7 @@ class TypolinkViewHelper extends AbstractViewHelper $this->registerArgument('addQueryStringMethod', 'string', '', false, 'GET'); $this->registerArgument('addQueryStringExclude', 'string', '', false, ''); $this->registerArgument('absolute', 'bool', 'Ensure the resulting URL is an absolute URL', false, false); + $this->registerArgument('parts-as', 'string', 'Variable name containing typoLink parts (if any)', false, 'typoLinkParts'); } /** @@ -118,6 +119,7 @@ class TypolinkViewHelper extends AbstractViewHelper trigger_error('Using the argument "useCacheHash" in <f:link.typolink> ViewHelper has no effect anymore. Remove the argument in your fluid template, as it will result in a fatal error.', E_USER_DEPRECATED); } $parameter = $arguments['parameter'] ?? ''; + $partsAs = $arguments['parts-as'] ?? 'typoLinkParts'; $typoLinkCodec = GeneralUtility::makeInstance(TypoLinkCodecService::class); $typoLinkConfiguration = $typoLinkCodec->decode($parameter); @@ -125,8 +127,14 @@ class TypolinkViewHelper extends AbstractViewHelper $mergedTypoLinkConfiguration = static::mergeTypoLinkConfiguration($typoLinkConfiguration, $arguments); $typoLinkParameter = $typoLinkCodec->encode($mergedTypoLinkConfiguration); + // expose internal typoLink configuration to Fluid child context + $variableProvider = $renderingContext->getVariableProvider(); + $variableProvider->add($partsAs, $typoLinkConfiguration); // If no link has to be rendered, the inner content will be returned as such $content = (string)$renderChildrenClosure(); + // clean up exposed variables + $variableProvider->remove($partsAs); + if ($parameter) { $content = static::invokeContentObjectRenderer($arguments, $typoLinkParameter, $content); } diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Fixtures/link_typolink_parts.html b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Fixtures/link_typolink_parts.html new file mode 100644 index 0000000000000000000000000000000000000000..55ca7bb7c0d64a98f89da6ee3b1a1f5c88859703 --- /dev/null +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Fixtures/link_typolink_parts.html @@ -0,0 +1,6 @@ +<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> + +<f:link.typolink parameter="{parameter}" + parts-as="typoLinkParts">Individual {typoLinkParts.target} {typoLinkParts.class} {typoLinkParts.title}</f:link.typolink> + +</html> diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php index 7807dc489eeadffb3305316b1955b08b4fa15a2c..50aab657a6f0d2b8269c9b46f7e502bd2faa38c4 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/TypolinkViewHelperTest.php @@ -343,4 +343,33 @@ class TypolinkViewHelperTest extends FunctionalTestCase ], ]; } + + public function typoLinkPartsAreRenderedDataProvider(): array + { + return [ + [ + 'http://typo3.org/ "_self" "<CSS>" "<Title>"', + '<a href="http://typo3.org/" title="<Title>" target="_self" class="<CSS>">Individual _self <CSS> <Title></a>', + ], + [ + 'http://typo3.org/ "<Target>" "<CSS>" "<Title>"', // target does not point to "self", adds noreferrer relationship + '<a href="http://typo3.org/" title="<Title>" target="<Target>" class="<CSS>" rel="noreferrer">Individual <Target> <CSS> <Title></a>', + ], + ]; + } + + /** + * @param string $parameter + * @param string $expectation + * + * @test + * @dataProvider typoLinkPartsAreRenderedDataProvider + */ + public function typoLinkPartsAreRendered(string $parameter, string $expectation): void + { + $view = new StandaloneView(); + $view->setTemplatePathAndFilename('typo3/sysext/fluid/Tests/Functional/ViewHelpers/Fixtures/link_typolink_parts.html'); + $view->assignMultiple(['parameter' => $parameter]); + self::assertSame($expectation, trim($view->render())); + } } diff --git a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php index 998f3a1b8712fd5b04902bf8ea1a0c7c638e034b..b84fe664c0adcd3a21a5d1a6ed92fe8440094f4e 100644 --- a/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php @@ -15,11 +15,13 @@ namespace TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\Link; * The TYPO3 project - inspiring people to share! */ +use PHPUnit\Framework\MockObject\MockObject; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext; use TYPO3\CMS\Fluid\ViewHelpers\Link\TypolinkViewHelper; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use TYPO3\TestingFramework\Fluid\Unit\ViewHelpers\ViewHelperBaseTestcase; +use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface; /** * Class TypolinkViewHelperTest @@ -27,7 +29,7 @@ use TYPO3\TestingFramework\Fluid\Unit\ViewHelpers\ViewHelperBaseTestcase; class TypolinkViewHelperTest extends ViewHelperBaseTestcase { /** - * @var TypolinkViewHelper|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface + * @var TypolinkViewHelper|MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */ protected $subject; @@ -37,8 +39,11 @@ class TypolinkViewHelperTest extends ViewHelperBaseTestcase protected function setUp(): void { $this->subject = $this->getAccessibleMock(TypolinkViewHelper::class, ['renderChildren']); - /** @var RenderingContext $renderingContext */ + /** @var VariableProviderInterface|MockObject $variableProvider */ + $variableProvider = $this->getMockBuilder(VariableProviderInterface::class)->getMock(); + /** @var RenderingContext|MockObject $renderingContext */ $renderingContext = $this->getMockBuilder(RenderingContext::class)->disableOriginalConstructor()->getMock(); + $renderingContext->expects(self::any())->method('getVariableProvider')->willReturn($variableProvider); $this->subject->setRenderingContext($renderingContext); }