diff --git a/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99040-DeprecatedTypoScriptSetupConstantsTop-level-object.rst b/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99040-DeprecatedTypoScriptSetupConstantsTop-level-object.rst new file mode 100644 index 0000000000000000000000000000000000000000..43dc1a8c84752239a38a6a4c309ff9bdb4f9b543 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99040-DeprecatedTypoScriptSetupConstantsTop-level-object.rst @@ -0,0 +1,92 @@ +.. include:: /Includes.rst.txt + +.. _deprecation-99040-1668076207: + +============================================================================== +Deprecation: #99040 - Deprecated TypoScript setup "constants" top-level-object +============================================================================== + +See :issue:`99040` + +Description +=========== + +The Frontend TypoScript setup (!) top-level-object :typoscript:`constants` can be +used to define constants for replacement inside a :typoscript:`parseFunc`. +If :typoscript:`parseFunc` somewhere is configured with :typoscript:`.constants = 1`, +then all occurrences of the constant in the text will be substituted with the +actual value. + +This construct has been marked as deprecated in TYPO3 v12 and will be removed with v13. + + +Impact +====== + +Using the :typoscript:`constants` top-level-object in combination with the +:typoscript:`constants = 1` in :typoscript:`parseFunc` to substitute strings +like :typoscript:`###MY_CONSTANT###` triggers a deprecation level log error +in TYPO3 v12 and will stop working in v13. + + +Affected installations +====================== + +This is a relatively rarely used feature, not well-known by many integrators. +TYPO3 integrators should watch out for :typoscript:`###` markers within +TypoScript, the Backend Template module search functionality should help here. + +The Template Analyzer will also show usages of the setup top-level-object +:typoscript:`constants`. + + +Migration +========= + +One possible solution is to switch to TypoScript constants / settings instead +for simple cases. + +A simple example usage before: + +.. code-block:: typoscript + + TypoScript setup: + + constants.EMAIL = mail@example.com + page = PAGE + page.10 = TEXT + page.10.value = Write an email to ###EMAIL### + page.10.parseFunc.constants = 1 + +Switching to a TypoScript constant / setting: + +.. code-block:: typoscript + + TypoScript constants / settings: + + myEmail = mail@example.com + + TypoScript setup: + + page = PAGE + page.10 = TEXT + page.10.value = Write an email to {$myEmail} + +The main usage of this feature has been a "magic" substitution within :typoscript:`lib.parseFunc_RTE`: +When :sql:`tt_content` rich text content elements contain such substitution strings, they are +replaced by :typoscript:`parseFunc` accordingly. For instance, a tt_content RTE element with the +content `Send an email to ###EMAIL###` would substitute to `Send an email to email@example.com` *if* +the top-level setup :typoscript:`constants` object has been set up. This substitution +relies on the the fact that editors actively know about and use this construct: If only one content +element did not prepare for this - since an editor forgot or hasn't been trained about it, changing +such a constant on TypoScript level would still lead to faulty Frontend output, rendering the +entire substitution approach useless. + +In case instances still rely on this magic substitution principle, and made sure all editors +always know and follow this approach, instances can use the :typoscript:`userFunc` +property of :typoscript:`parseFunc` to re-implement the functionality: Basically by +copying the deprecated code to an own class and registering the :typoscript:`userFunc` +in :typoscript:`lib.parseFunc_RTE`. + + +.. index:: TypoScript, NotScanned, ext:frontend diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Format/HtmlViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Format/HtmlViewHelper.php index ba7f393066766b1eebdf778bc3b525882450ba02..30c2d0162a20e6553244b7595825265060c67c63 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Format/HtmlViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Format/HtmlViewHelper.php @@ -42,20 +42,20 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; * * :: * - * <f:format.html>###PROJECT### is a cool <b>CMS</b> (<a href="https://www.typo3.org">TYPO3</a>).</f:format.html> + * <f:format.html>{$myConstant.project} is a cool <b>CMS</b> (<a href="https://www.typo3.org">TYPO3</a>).</f:format.html> * * Output:: * * <p class="bodytext">TYPO3 is a cool <strong>CMS</strong> (<a href="https://www.typo3.org" target="_blank">TYPO3</a>).</p> * - * Depending on TYPO3 setup. + * Depending on TYPO3 constants. * * Custom parseFunc * ---------------- * * :: * - * <f:format.html parseFuncTSPath="lib.parseFunc">###PROJECT### is a cool <b>CMS</b> (<a href="https://www.typo3.org">TYPO3</a>).</f:format.html> + * <f:format.html parseFuncTSPath="lib.parseFunc">TYPO3 is a cool <b>CMS</b> (<a href="https://www.typo3.org">TYPO3</a>).</f:format.html> * * Output:: * diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/HtmlViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/HtmlViewHelperTest.php index 5ea83ab0b16a4c3cbda691aec522207b9167ba34..b3119b0cb095a69243cccd9df6dd488c27ff97e2 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/HtmlViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/HtmlViewHelperTest.php @@ -37,7 +37,7 @@ class HtmlViewHelperTest extends FunctionalTestCase { return [ 'format.html: process lib.parseFunc_RTE by default' => [ - '<f:format.html>###PROJECT### is a cool CMS</f:format.html>', + '<f:format.html>{$project} is a cool CMS</f:format.html>', 'TYPO3 is a cool CMS', ], 'format.html: process inline notation with undefined variable returns empty string' => [ @@ -45,7 +45,7 @@ class HtmlViewHelperTest extends FunctionalTestCase '', ], 'format.html: process specific TS path' => [ - '<f:format.html parseFuncTSPath="lib.foo">###FOO### is BAR</f:format.html>', + '<f:format.html parseFuncTSPath="lib.foo">{$foo} is BAR</f:format.html>', 'BAR is BAR', ], 'format.html: specific TS path with current' => [ @@ -61,8 +61,8 @@ class HtmlViewHelperTest extends FunctionalTestCase 'Hello Bar', ], 'format.html: specific TS path with data, currentValueKey and a constant' => [ - '<f:format.html parseFuncTSPath="lib.news" data="{uid: 1, pid: 12, title: \'Greate news\'}" currentValueKey="title">###PROJECT### news:</f:format.html>', - 'TYPO3 news: Greate news', + '<f:format.html parseFuncTSPath="lib.news" data="{uid: 1, pid: 12, title: \'Great news\'}" currentValueKey="title">{$project} news:</f:format.html>', + 'TYPO3 news: Great news', ], // table attribute is hard to test. It was only used as parent for CONTENT and RECORD cObj. // Further the table will be used in FILES cObj as fallback, if a table was not given in references array. @@ -135,16 +135,16 @@ class HtmlViewHelperTest extends FunctionalTestCase 'pid' => 1, 'root' => 1, 'clear' => 3, + 'constants' => <<<EOT +project = TYPO3 +foo = BAR +EOT, 'config' => <<<EOT -constants.PROJECT = TYPO3 -constants.FOO = BAR lib.parseFunc_RTE { htmlSanitize = 1 - constants = 1 } lib.foo { htmlSanitize = 1 - constants = 1 } lib.inventor { htmlSanitize = 1 @@ -158,7 +158,6 @@ lib.record { } lib.news { htmlSanitize = 1 - constants = 1 plainTextStdWrap.noTrimWrap = || | plainTextStdWrap.dataWrap = |{CURRENT:1} } diff --git a/typo3/sysext/fluid_styled_content/Configuration/TypoScript/Helper/ParseFunc.typoscript b/typo3/sysext/fluid_styled_content/Configuration/TypoScript/Helper/ParseFunc.typoscript index b06e8cebf1775cc3ccb3b3f001396ff58c802155..548e5dd28450df465463e262b6485bf9c9700b59 100644 --- a/typo3/sysext/fluid_styled_content/Configuration/TypoScript/Helper/ParseFunc.typoscript +++ b/typo3/sysext/fluid_styled_content/Configuration/TypoScript/Helper/ParseFunc.typoscript @@ -32,6 +32,7 @@ lib.parseFunc { } allowTags = {$styles.content.allowTags} denyTags = * + # @deprecated since TYPO3 v12, remove with v13 constants = 1 nonTypoTagStdWrap { HTMLparser = 1 diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index aa1f46eac01778eee308b5ff9ae2550064af7801..6d1532c163d7701b54a86832339f86e5d275ee46 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -2585,11 +2585,8 @@ class ContentObjectRenderer implements LoggerAwareInterface * Implements the "if" function in TYPO3 TypoScript * * @param array $conf TypoScript properties defining what to compare - * @return bool - * @see stdWrap() - * @see _parseFunc() */ - public function checkIf($conf) + public function checkIf($conf): bool { if (!is_array($conf)) { return true; @@ -3081,15 +3078,14 @@ class ContentObjectRenderer implements LoggerAwareInterface * This situation has not become better by having a RTE around... * * This function is actually just splitting the input content according to the configuration of "external blocks". - * This means that before the input string is actually "parsed" it will be splitted into the parts configured to BE parsed + * This means that before the input string is actually "parsed" it will be split into the parts configured to BE parsed * (while other parts/blocks should NOT be parsed). - * Therefore the actual processing of the parseFunc properties goes on in ->_parseFunc() + * Therefore, the actual processing of the parseFunc properties goes on in ->parseFuncInternal() * * @param string $theValue The value to process. * @param non-empty-array<string, mixed>|null $conf TypoScript configuration for parseFunc * @param non-empty-string|null $ref Reference to get configuration from. Eg. "< lib.parseFunc" which means that the configuration of the object path "lib.parseFunc" will be retrieved and MERGED with what is in $conf! * @return string The processed value - * @see _parseFunc() */ public function parseFunc($theValue, ?array $conf, ?string $ref = null) { @@ -3110,7 +3106,7 @@ class ContentObjectRenderer implements LoggerAwareInterface $conf['htmlSanitize'] = (bool)($conf['htmlSanitize'] ?? true); // Process: if ((string)($conf['externalBlocks'] ?? '') === '') { - $result = $this->_parseFunc($theValue, $conf); + $result = $this->parseFuncInternal($theValue, $conf); if ($conf['htmlSanitize']) { $result = $this->stdWrap_htmlSanitize($result, $conf['htmlSanitize.'] ?? []); } @@ -3199,7 +3195,7 @@ class ContentObjectRenderer implements LoggerAwareInterface $parts[$k] = $this->stdWrap($parts[$k], $cfg['stdWrap.']); } } else { - $parts[$k] = $this->_parseFunc($parts[$k], $conf); + $parts[$k] = $this->parseFuncInternal($parts[$k], $conf); } } $result = implode('', $parts); @@ -3216,9 +3212,8 @@ class ContentObjectRenderer implements LoggerAwareInterface * @param array $conf TypoScript configuration for parseFunc * @return string The processed value * @internal - * @see parseFunc() */ - public function _parseFunc($theValue, $conf) + protected function parseFuncInternal($theValue, $conf) { if (!empty($conf['if.']) && !$this->checkIf($conf['if.'])) { return $theValue; @@ -3274,6 +3269,13 @@ class ContentObjectRenderer implements LoggerAwareInterface } $tmpConstants = $typoScriptSetupArray['constants.'] ?? null; if (!empty($conf['constants']) && is_array($tmpConstants)) { + // @deprecated since v12, remove with v13: Entire if plus init code above + trigger_error( + 'The TypoScript setup "constants" top-level-object and the parseFunc property "constants" have' + . ' been deprecated in TYPO3 v12 and will be removed in v12. Use TypoScript constants / settings' + . ' and access them in setup using "{$myConstant}" instead.', + E_USER_DEPRECATED + ); foreach ($tmpConstants as $key => $val) { if (is_string($val)) { $data = str_replace('###' . $key . '###', $val, $data); @@ -3305,7 +3307,7 @@ class ContentObjectRenderer implements LoggerAwareInterface foreach ($conf['tags.'] as $tag => $tagConfig) { // only match tag `a` in `<a href"...">` but not in `<abbr>` if (preg_match('#<' . $tag . '[\s/>]#', $data)) { - $data = $this->_parseFunc($data, $conf); + $data = $this->parseFuncInternal($data, $conf); break; } } @@ -3553,12 +3555,14 @@ class ContentObjectRenderer implements LoggerAwareInterface * Will find all strings prefixed with "http://" and "https://" in the $data string and make them into a link, * linking to the URL we should have found. * + * Helper method of parseFuncInternal(). + * * @param string $data The string in which to search for "http:// * @param array $conf Configuration for makeLinks, see link * @return string The processed input string, being returned. - * @see _parseFunc() + * @internal */ - public function http_makelinks($data, $conf) + protected function http_makelinks(string $data, array $conf): string { $parts = []; foreach (['http://', 'https://'] as $scheme) { @@ -3607,19 +3611,21 @@ class ContentObjectRenderer implements LoggerAwareInterface * Will find all strings prefixed with "mailto:" in the $data string and make them into a link, * linking to the email address they point to. * + * Helper method of parseFuncInternal(). + * * @param string $data The string in which to search for "mailto: * @param array $conf Configuration for makeLinks, see link * @return string The processed input string, being returned. - * @see _parseFunc() + * @internal */ - public function mailto_makelinks($data, $conf) + protected function mailto_makelinks(string $data, array $conf): string { $conf = (array)$conf; $parts = []; // split by mailto logic $textpieces = explode('mailto:', $data); $pieces = count($textpieces); - $textstr = $textpieces[0]; + $textstr = $textpieces[0] ?? ''; for ($i = 1; $i < $pieces; $i++) { $len = strcspn($textpieces[$i], chr(32) . "\t" . CRLF); if (trim(substr($textstr, -1)) === '' && $len) { @@ -4607,9 +4613,6 @@ class ContentObjectRenderer implements LoggerAwareInterface * @param array $conf The TypoScript configuration to pass the function * @param mixed $content The content payload to pass the function * @return mixed The return content from the function call. Should probably be a string. - * @see stdWrap() - * @see typoLink() - * @see _parseFunc() */ public function callUserFunction($funcName, $conf, $content) { @@ -5676,10 +5679,9 @@ class ContentObjectRenderer implements LoggerAwareInterface /** * Get content length of the current tag that could also contain nested tag contents * - * @param string $theValue - * @param int $pointer - * @param string $currentTag - * @return int + * Helper method of parseFuncInternal(). + * + * @internal */ protected function getContentLengthOfCurrentTag(string $theValue, int $pointer, string $currentTag): int { diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php index a556f77dc7df763fcc64e479b49d292dbe109152..e48a0be9c5edcc67ccff66794686622586470231 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php @@ -2324,7 +2324,7 @@ class ContentObjectRendererTest extends UnitTestCase $linkFactory->method('create')->willReturn($linkResult); GeneralUtility::addInstance(LinkFactory::class, $linkFactory); - self::assertSame($expectedResult, $this->subject->http_makelinks($data, $configuration)); + self::assertSame($expectedResult, $this->subject->_call('http_makelinks', $data, $configuration)); } public function invalidHttpMakelinksDataProvider(): array @@ -2357,7 +2357,7 @@ class ContentObjectRendererTest extends UnitTestCase */ public function httpMakelinksReturnsNoLink(string $data, array $configuration, string $expectedResult): void { - self::assertSame($expectedResult, $this->subject->http_makelinks($data, $configuration)); + self::assertSame($expectedResult, $this->subject->_call('http_makelinks', $data, $configuration)); } public function mailtoMakelinksDataProvider(): array @@ -2436,7 +2436,7 @@ class ContentObjectRendererTest extends UnitTestCase $linkFactory->method('create')->willReturn($linkResult); GeneralUtility::addInstance(LinkFactory::class, $linkFactory); - self::assertSame($expectedResult, $this->subject->mailto_makelinks($data, $configuration)); + self::assertSame($expectedResult, $this->subject->_call('mailto_makelinks', $data, $configuration)); } /** @@ -2444,7 +2444,7 @@ class ContentObjectRendererTest extends UnitTestCase */ public function mailtoMakelinksReturnsNoMailToLink(): void { - self::assertSame('mailto:', $this->subject->mailto_makelinks('mailto:', [])); + self::assertSame('mailto:', $this->subject->_call('mailto_makelinks', 'mailto:', [])); } /** @@ -5520,7 +5520,7 @@ class ContentObjectRendererTest extends UnitTestCase $content, [], 0, - null, + false, ], 'if. is empty array' => [ $content, @@ -5528,7 +5528,7 @@ class ContentObjectRendererTest extends UnitTestCase $content, ['if.' => []], 0, - null, + false, ], 'if. is null' => [ $content, @@ -5536,7 +5536,7 @@ class ContentObjectRendererTest extends UnitTestCase $content, ['if.' => null], 0, - null, + false, ], 'if. is false' => [ $content, @@ -5544,7 +5544,7 @@ class ContentObjectRendererTest extends UnitTestCase $content, ['if.' => false], 0, - null, + false, ], 'if. is 0' => [ $content, @@ -5552,7 +5552,7 @@ class ContentObjectRendererTest extends UnitTestCase $content, ['if.' => false], 0, - null, + false, ], 'if. is "0"' => [ $content, @@ -5560,7 +5560,7 @@ class ContentObjectRendererTest extends UnitTestCase $content, ['if.' => '0'], 0, - null, + false, ], 'checkIf returning true' => [ $content, @@ -5600,9 +5600,9 @@ class ContentObjectRendererTest extends UnitTestCase * @param string $content The given content. * @param array $conf * @param int $times Times checkIf is called (0 or 1). - * @param bool|null $will Return of checkIf (null if not called). + * @param bool $will Return of checkIf (null if not called). */ - public function stdWrap_if(string $expect, bool $stop, string $content, array $conf, int $times, ?bool $will): void + public function stdWrap_if(string $expect, bool $stop, string $content, array $conf, int $times, bool $will): void { $subject = $this->getAccessibleMock( ContentObjectRenderer::class,