diff --git a/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php b/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php index c4f4e9f5d6e05579ab194d2c5e13e6913c6588fc..b8979776be255c58630af355b6aec3d3fd74ea57 100644 --- a/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php +++ b/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php @@ -13,7 +13,9 @@ namespace TYPO3\CMS\Core\Service; * * The TYPO3 project - inspiring people to share! */ +use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\MathUtility; /** * Helper functionality for subparts and marker substitution @@ -307,4 +309,210 @@ class MarkerBasedTemplateService return $result; } + + /** + * Multi substitution function with caching. + * + * This function should be a one-stop substitution function for working + * with HTML-template. It does not substitute by str_replace but by + * splitting. This secures that the value inserted does not themselves + * contain markers or subparts. + * + * Note that the "caching" won't cache the content of the substition, + * but only the splitting of the template in various parts. So if you + * want only one cache-entry per template, make sure you always pass the + * exact same set of marker/subpart keys. Else you will be flooding the + * user's cache table. + * + * This function takes three kinds of substitutions in one: + * $markContentArray is a regular marker-array where the 'keys' are + * substituted in $content with their values + * + * $subpartContentArray works exactly like markContentArray only is whole + * subparts substituted and not only a single marker. + * + * $wrappedSubpartContentArray is an array of arrays with 0/1 keys where + * the subparts pointed to by the main key is wrapped with the 0/1 value + * alternating. + * + * @param string $content The content stream, typically HTML template content. + * @param array $markContentArray Regular marker-array where the 'keys' are substituted in $content with their values + * @param array $subpartContentArray Exactly like markContentArray only is whole subparts substituted and not only a single marker. + * @param array $wrappedSubpartContentArray An array of arrays with 0/1 keys where the subparts pointed to by the main key is wrapped with the 0/1 value alternating. + * @return string The output content stream + * @see substituteSubpart(), substituteMarker(), substituteMarkerInObject(), TEMPLATE() + */ + public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null) + { + $runtimeCache = $this->getRuntimeCache(); + // If not arrays then set them + if (is_null($markContentArray)) { + // Plain markers + $markContentArray = []; + } + if (is_null($subpartContentArray)) { + // Subparts being directly substituted + $subpartContentArray = []; + } + if (is_null($wrappedSubpartContentArray)) { + // Subparts being wrapped + $wrappedSubpartContentArray = []; + } + // Finding keys and check hash: + $sPkeys = array_keys($subpartContentArray); + $wPkeys = array_keys($wrappedSubpartContentArray); + $keysToReplace = array_merge(array_keys($markContentArray), $sPkeys, $wPkeys); + if (empty($keysToReplace)) { + return $content; + } + asort($keysToReplace); + $storeKey = md5('substituteMarkerArrayCached_storeKey:' . serialize([$content, $keysToReplace])); + if ($runtimeCache->get($storeKey)) { + $storeArr = $runtimeCache->get($storeKey); + } else { + $cache = $this->getCache(); + $storeArrDat = $cache->get($storeKey); + if (is_array($storeArrDat)) { + $storeArr = $storeArrDat; + // Setting the data in the first level cache + $runtimeCache->set($storeKey, $storeArr); + } else { + // Finding subparts and substituting them with the subpart as a marker + foreach ($sPkeys as $sPK) { + $content = $this->substituteSubpart($content, $sPK, $sPK); + } + // Finding subparts and wrapping them with markers + foreach ($wPkeys as $wPK) { + $content = $this->substituteSubpart($content, $wPK, [ + $wPK, + $wPK + ]); + } + + $storeArr = []; + // search all markers in the content + $result = preg_match_all('/###([^#](?:[^#]*+|#{1,2}[^#])+)###/', $content, $markersInContent); + if ($result !== false && !empty($markersInContent[1])) { + $keysToReplaceFlipped = array_flip($keysToReplace); + $regexKeys = []; + $wrappedKeys = []; + // Traverse keys and quote them for reg ex. + foreach ($markersInContent[1] as $key) { + if (isset($keysToReplaceFlipped['###' . $key . '###'])) { + $regexKeys[] = preg_quote($key, '/'); + $wrappedKeys[] = '###' . $key . '###'; + } + } + $regex = '/###(?:' . implode('|', $regexKeys) . ')###/'; + $storeArr['c'] = preg_split($regex, $content); // contains all content parts around markers + $storeArr['k'] = $wrappedKeys; // contains all markers incl. ### + // Setting the data inside the second-level cache + $runtimeCache->set($storeKey, $storeArr); + // Storing the cached data permanently + $cache->set($storeKey, $storeArr, ['substMarkArrayCached'], 0); + } + } + } + if (!empty($storeArr['k']) && is_array($storeArr['k'])) { + // Substitution/Merging: + // Merging content types together, resetting + $valueArr = array_merge($markContentArray, $subpartContentArray, $wrappedSubpartContentArray); + $wSCA_reg = []; + $content = ''; + // Traversing the keyList array and merging the static and dynamic content + foreach ($storeArr['k'] as $n => $keyN) { + // add content before marker + $content .= $storeArr['c'][$n]; + if (!is_array($valueArr[$keyN])) { + // fetch marker replacement from $markContentArray or $subpartContentArray + $content .= $valueArr[$keyN]; + } else { + if (!isset($wSCA_reg[$keyN])) { + $wSCA_reg[$keyN] = 0; + } + // fetch marker replacement from $wrappedSubpartContentArray + $content .= $valueArr[$keyN][$wSCA_reg[$keyN] % 2]; + $wSCA_reg[$keyN]++; + } + } + // add remaining content + $content .= $storeArr['c'][count($storeArr['k'])]; + } + return $content; + } + + /** + * Substitute marker array in an array of values + * + * @param mixed $tree If string, then it just calls substituteMarkerArray. If array(and even multi-dim) then for each key/value pair the marker array will be substituted (by calling this function recursively) + * @param array $markContentArray The array of key/value pairs being marker/content values used in the substitution. For each element in this array the function will substitute a marker in the content string/array values. + * @return mixed The processed input variable. + * @see substituteMarker() + */ + public function substituteMarkerInObject(&$tree, array $markContentArray) + { + if (is_array($tree)) { + foreach ($tree as $key => $value) { + $this->substituteMarkerInObject($tree[$key], $markContentArray); + } + } else { + $tree = $this->substituteMarkerArray($tree, $markContentArray); + } + return $tree; + } + + /** + * Adds elements to the input $markContentArray based on the values from + * the fields from $fieldList found in $row + * + * @param array $markContentArray Array with key/values being marker-strings/substitution values. + * @param array $row An array with keys found in the $fieldList (typically a record) which values should be moved to the $markContentArray + * @param string $fieldList A list of fields from the $row array to add to the $markContentArray array. If empty all fields from $row will be added (unless they are integers) + * @param bool $nl2br If set, all values added to $markContentArray will be nl2br()'ed + * @param string $prefix Prefix string to the fieldname before it is added as a key in the $markContentArray. Notice that the keys added to the $markContentArray always start and end with "### + * @param bool $htmlSpecialCharsValue If set, all values are passed through htmlspecialchars() - RECOMMENDED to avoid most obvious XSS and maintain XHTML compliance. + * @param bool $respectXhtml if set, and $nl2br is set, then the new lines are added with <br /> instead of <br> + * @return array The modified $markContentArray + */ + public function fillInMarkerArray(array $markContentArray, array $row, $fieldList = '', $nl2br = true, $prefix = 'FIELD_', $htmlSpecialCharsValue = false, $respectXhtml = false) + { + if ($fieldList) { + $fArr = GeneralUtility::trimExplode(',', $fieldList, true); + foreach ($fArr as $field) { + $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($row[$field], $respectXhtml) : $row[$field]; + } + } else { + if (is_array($row)) { + foreach ($row as $field => $value) { + if (!MathUtility::canBeInterpretedAsInteger($field)) { + if ($htmlSpecialCharsValue) { + $value = htmlspecialchars($value); + } + $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($value, $respectXhtml) : $value; + } + } + } + } + return $markContentArray; + } + + /** + * Second-level cache + * + * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface + */ + protected function getCache() + { + return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash'); + } + + /** + * First-level cache (runtime cache) + * + * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface + */ + protected function getRuntimeCache() + { + return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime'); + } } diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80527-Marker-relatedMethodsInContentObjectRenderer.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80527-Marker-relatedMethodsInContentObjectRenderer.rst new file mode 100644 index 0000000000000000000000000000000000000000..eb940986e0caa9a65854db87e17f59c2396f21bb --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80527-Marker-relatedMethodsInContentObjectRenderer.rst @@ -0,0 +1,43 @@ +.. include:: ../../Includes.txt + +===================================================================== +Deprecation: #80527 - Marker-related methods in ContentObjectRenderer +===================================================================== + +See :issue:`80527` + +Description +=========== + +The following methods within php:`ContentObjectRenderer` PHP class have been marked as deprecated: + +* getSubpart() +* substituteSubpart() +* substituteSubpartArray() +* substituteMarker() +* substituteMarkerArrayCached() +* substituteMarkerArray() +* substituteMarkerInObject() +* substituteMarkerAndSubpartArrayRecursive() +* fillInMarkerArray() + + +Impact +====== + +Calling any of the methods above will trigger a deprecation log entry. + + +Affected Installations +====================== + +Any installation using custom extensions calling these API methods. + + +Migration +========= + +Instantiate the class `MarkerBasedTemplateService` available in TYPO3 v7, which contains equivalents +to all methods that have been marked as deprecated with the same functionality and namings. + +.. index:: PHP-API \ No newline at end of file diff --git a/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php b/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php index 1cd4496905cfacb8f8e8ca9f38725975df106da0..032c0e3bd3a04d6f9af8a4103bd7f49221879ff1 100644 --- a/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php +++ b/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php @@ -14,6 +14,10 @@ namespace TYPO3\CMS\Core\Tests\Unit\Service; * The TYPO3 project - inspiring people to share! */ +use Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -27,9 +31,29 @@ class MarkerBasedTemplateServiceTest extends \TYPO3\TestingFramework\Core\Unit\U */ protected $templateService; + /** + * @var array A backup of registered singleton instances + */ + protected $singletonInstances = []; + protected function setUp() { - $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); + $this->singletonInstances = GeneralUtility::getSingletonInstances(); + + /** @var CacheManager|ObjectProphecy $cacheManagerProphecy */ + $cacheManagerProphecy = $this->prophesize(CacheManager::class); + GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal()); + $cacheFrontendProphecy = $this->prophesize(FrontendInterface::class); + $cacheManagerProphecy->getCache(Argument::cetera())->willReturn($cacheFrontendProphecy->reveal()); + + $this->templateService = new MarkerBasedTemplateService(); + } + + protected function tearDown() + { + GeneralUtility::purgeInstances(); + GeneralUtility::resetSingletonInstances($this->singletonInstances); + parent::tearDown(); } /** @@ -605,4 +629,121 @@ Value 2.2 { $this->assertSame($expected, $this->templateService->substituteMarkerAndSubpartArrayRecursive($template, $markersAndSubparts, $wrap, $uppercase, $deleteUnused)); } + + /** + * @return array + */ + public function substituteMarkerArrayCachedReturnsExpectedContentDataProvider() + { + return [ + 'no markers defined' => [ + 'dummy content with ###UNREPLACED### marker', + [], + [], + [], + 'dummy content with ###UNREPLACED### marker', + ], + 'no markers used' => [ + 'dummy content with no marker', + [ + '###REPLACED###' => '_replaced_' + ], + [], + [], + 'dummy content with no marker', + ], + 'one marker' => [ + 'dummy content with ###REPLACED### marker', + [ + '###REPLACED###' => '_replaced_' + ], + [], + [], + 'dummy content with _replaced_ marker' + ], + 'one marker with lots of chars' => [ + 'dummy content with ###RE.:##-=_()LACED### marker', + [ + '###RE.:##-=_()LACED###' => '_replaced_' + ], + [], + [], + 'dummy content with _replaced_ marker' + ], + 'markers which are special' => [ + 'dummy ###aa##.#######A### ######', + [ + '###aa##.###' => 'content ', + '###A###' => 'is', + '######' => '-is not considered-' + ], + [], + [], + 'dummy content #is ######' + ], + 'two markers in content, but more defined' => [ + 'dummy ###CONTENT### with ###REPLACED### marker', + [ + '###REPLACED###' => '_replaced_', + '###CONTENT###' => 'content', + '###NEVERUSED###' => 'bar' + ], + [], + [], + 'dummy content with _replaced_ marker' + ], + 'one subpart' => [ + 'dummy content with ###ASUBPART### around some text###ASUBPART###.', + [], + [ + '###ASUBPART###' => 'some other text' + ], + [], + 'dummy content with some other text.' + ], + 'one wrapped subpart' => [ + 'dummy content with ###AWRAPPEDSUBPART### around some text###AWRAPPEDSUBPART###.', + [], + [], + [ + '###AWRAPPEDSUBPART###' => [ + 'more content', + 'content' + ] + ], + 'dummy content with more content around some textcontent.' + ], + 'one subpart with markers, not replaced recursively' => [ + 'dummy ###CONTENT### with ###ASUBPART### around ###SOME### text###ASUBPART###.', + [ + '###CONTENT###' => 'content', + '###SOME###' => '-this should never make it into output-', + '###OTHER_NOT_REPLACED###' => '-this should never make it into output-' + ], + [ + '###ASUBPART###' => 'some ###OTHER_NOT_REPLACED### text' + ], + [], + 'dummy content with some ###OTHER_NOT_REPLACED### text.' + ], + ]; + } + + /** + * @test + * @dataProvider substituteMarkerArrayCachedReturnsExpectedContentDataProvider + * + * @param string $content + * @param array $markContentArray + * @param array $subpartContentArray + * @param array $wrappedSubpartContentArray + * @param string $expectedContent + * @param bool $shouldQueryCache + * @param bool $shouldStoreCache + */ + public function substituteMarkerArrayCachedReturnsExpectedContent($content, array $markContentArray, array $subpartContentArray, array $wrappedSubpartContentArray, $expectedContent) + { + $resultContent = $this->templateService->substituteMarkerArrayCached($content, $markContentArray, $subpartContentArray, $wrappedSubpartContentArray); + $this->assertSame($expectedContent, $resultContent); + } } diff --git a/typo3/sysext/css_styled_content/Classes/Controller/CssStyledContentController.php b/typo3/sysext/css_styled_content/Classes/Controller/CssStyledContentController.php index c9665597a94eaa3b1db911cd5426eee6661747c2..7f4fc5deaa6eaf9f91f4077155127a78201d0b94 100644 --- a/typo3/sysext/css_styled_content/Classes/Controller/CssStyledContentController.php +++ b/typo3/sysext/css_styled_content/Classes/Controller/CssStyledContentController.php @@ -614,13 +614,13 @@ class CssStyledContentController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlug // Get the caption if (!$renderGlobalCaption) { $imageMarkers['caption'] = $this->cObj->stdWrap($this->cObj->cObjGet($conf['caption.'], 'caption.'), $conf['caption.']); - $imageMarkers['caption'] = $this->cObj->substituteMarkerArray($imageMarkers['caption'], $captionMarkers, '###|###', 1, 1); + $imageMarkers['caption'] = $this->templateService->substituteMarkerArray($imageMarkers['caption'], $captionMarkers, '###|###', 1, 1); } if ($addClassesImageConf[$imagesCounter - 1]['addClassesImage']) { $imageMarkers['classes'] = ' ' . $addClassesImageConf[$imagesCounter - 1]['addClassesImage']; } } - $columnImages[] = $this->cObj->substituteMarkerArray($single, $imageMarkers, '###|###', 1, 1); + $columnImages[] = $this->templateService->substituteMarkerArray($single, $imageMarkers, '###|###', 1, 1); $currentImage++; } $rowColumn = $this->cObj->stdWrap(implode(LF, $columnImages), $conf['columnStdWrap.']); @@ -629,7 +629,7 @@ class CssStyledContentController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlug if ($addClassesColConf[$columnCounter - 1]['addClassesCol']) { $columnMarkers['classes'] = ' ' . $addClassesColConf[$columnCounter - 1]['addClassesCol']; } - $rowColumns[] = $this->cObj->substituteMarkerArray($rowColumn, $columnMarkers, '###|###', 1, 1); + $rowColumns[] = $this->templateService->substituteMarkerArray($rowColumn, $columnMarkers, '###|###', 1, 1); } if ($rowCounter == $rowCount) { $rowConfiguration = $conf['lastRowStdWrap.']; @@ -639,7 +639,7 @@ class CssStyledContentController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlug $row = $this->cObj->stdWrap(implode(LF, $rowColumns), $rowConfiguration); // Start filling the markers for columnStdWrap $rowMarkers = []; - $rows[] = $this->cObj->substituteMarkerArray($row, $rowMarkers, '###|###', 1, 1); + $rows[] = $this->templateService->substituteMarkerArray($row, $rowMarkers, '###|###', 1, 1); } $images = $this->cObj->stdWrap(implode(LF, $rows), $conf['allStdWrap.']); // Start filling the markers for allStdWrap @@ -681,7 +681,7 @@ class CssStyledContentController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlug $class = ' ' . implode(' ', $classes); } // Fill the markers for the allStdWrap - $images = $this->cObj->substituteMarkerArray($images, $allMarkers, '###|###', 1, 1); + $images = $this->templateService->substituteMarkerArray($images, $allMarkers, '###|###', 1, 1); } else { // Apply optionSplit to the list of classes that we want to add to each image $addClassesImage = $conf['addClassesImage']; diff --git a/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php b/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php index 9a8bdaecb9b21c2f8e00d248ba4b0de0d48cea82..925ad45aeead6cf9030a521d687dfd067100b0c6 100644 --- a/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php +++ b/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php @@ -215,7 +215,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin */ protected function showForgot() { - $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_FORGOT###'); + $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_FORGOT###'); $subpartArray = ($linkpartArray = []); $postData = GeneralUtility::_POST($this->prefixId); if ($postData['forgot_email']) { @@ -292,7 +292,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin $markerArray['###FORGOTHASH###'] = $hash; // Set hash in feuser session $this->frontendController->fe_user->setKey('ses', 'forgot_hash', ['forgot_hash' => $hash]); - return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); + return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); } /** @@ -306,7 +306,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin $subpartArray = ($linkpartArray = []); $done = false; $minLength = (int)$this->conf['newPasswordMinLength'] ?: 6; - $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_CHANGEPASSWORD###'); + $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_CHANGEPASSWORD###'); $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('change_password_header', $this->conf['changePasswordHeader_stdWrap.']); $markerArray['###STATUS_MESSAGE###'] = sprintf($this->getDisplayText( 'change_password_message', @@ -410,7 +410,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin } } } - return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); + return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); } /** @@ -501,7 +501,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin */ protected function showLogout() { - $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_LOGOUT###'); + $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_LOGOUT###'); $subpartArray = ($linkpartArray = []); $markerArray['###STATUS_HEADER###'] = $this->getDisplayText('status_header', $this->conf['logoutHeader_stdWrap.']); $markerArray['###STATUS_MESSAGE###'] = $this->getDisplayText('status_message', $this->conf['logoutMessage_stdWrap.']); @@ -521,7 +521,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin $markerArray['###ACTION_URI###'] = htmlspecialchars($this->redirectUrl); $this->redirectUrl = ''; } - return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); + return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); } /** @@ -531,7 +531,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin */ protected function showLogin() { - $subpart = $this->cObj->getSubpart($this->template, '###TEMPLATE_LOGIN###'); + $subpart = $this->templateService->getSubpart($this->template, '###TEMPLATE_LOGIN###'); $subpartArray = ($linkpartArray = ($markerArray = [])); $gpRedirectUrl = ''; $markerArray['###LEGEND###'] = htmlspecialchars($this->pi_getLL('oLabel_header_welcome')); @@ -659,7 +659,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin } else { $subpartArray['###PERMALOGIN_VALID###'] = ''; } - return $this->cObj->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); + return $this->templateService->substituteMarkerArrayCached($subpart, $markerArray, $subpartArray, $linkpartArray); } /** diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index aa49db419e65066e79d6b1aaa6e9a696cb756f03..fdedaec27b856432a1bfbdda62499aee29d58c2a 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -372,13 +372,6 @@ class ContentObjectRenderer */ public $lastTypoLinkLD = []; - /** - * Caching substituteMarkerArrayCached function - * - * @var array - */ - public $substMarkerCache = []; - /** * array that registers rendered content elements (or any table) to make sure they are not rendered recursively! * @@ -1088,7 +1081,7 @@ class ContentObjectRenderer 'selfClosingTagSlash' => (!empty($tsfe->xhtmlDoctype) ? ' /' : ''), ]; - $theValue = $this->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true); + $theValue = $this->templateService->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true); $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap']; if ($linkWrap) { @@ -1227,7 +1220,7 @@ class ContentObjectRenderer $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]); $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : ''; - $oneSourceCollection = $this->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true); + $oneSourceCollection = $this->templateService->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true); if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'])) { foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] as $classData) { @@ -1551,9 +1544,11 @@ class ContentObjectRenderer * @param string $content The content stream, typically HTML template content. * @param string $marker The marker string, typically on the form "###[the marker string]### * @return string The subpart found, if found. + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function getSubpart($content, $marker) { + GeneralUtility::logDeprecatedFunction(); return $this->templateService->getSubpart($content, $marker); } @@ -1568,9 +1563,11 @@ class ContentObjectRenderer * @param mixed $subpartContent The content to insert instead of the subpart found. If a string, then just plain substitution happens (includes removing the HTML comments of the subpart if found). If $subpartContent happens to be an array, it's [0] and [1] elements are wrapped around the EXISTING content of the subpart (fetched by getSubpart()) thereby not removing the original content. * @param bool|int $recursive If $recursive is set, the function calls itself with the content set to the remaining part of the content after the second marker. This means that proceding subparts are ALSO substituted! * @return string The processed HTML content string. + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteSubpart($content, $marker, $subpartContent, $recursive = 1) { + GeneralUtility::logDeprecatedFunction(); return $this->templateService->substituteSubpart($content, $marker, $subpartContent, $recursive); } @@ -1580,9 +1577,11 @@ class ContentObjectRenderer * @param string $content The content stream, typically HTML template content. * @param array $subpartsContent The array of key/value pairs being subpart/content values used in the substitution. For each element in this array the function will substitute a subpart in the content stream with the content. * @return string The processed HTML content string. + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteSubpartArray($content, array $subpartsContent) { + GeneralUtility::logDeprecatedFunction(); return $this->templateService->substituteSubpartArray($content, $subpartsContent); } @@ -1595,9 +1594,11 @@ class ContentObjectRenderer * @param mixed $markContent The content to insert instead of the marker string found. * @return string The processed HTML content string. * @see substituteSubpart() + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteMarker($content, $marker, $markContent) { + GeneralUtility::logDeprecatedFunction(); return $this->templateService->substituteMarker($content, $marker, $markContent); } @@ -1632,110 +1633,12 @@ class ContentObjectRenderer * @param array $wrappedSubpartContentArray An array of arrays with 0/1 keys where the subparts pointed to by the main key is wrapped with the 0/1 value alternating. * @return string The output content stream * @see substituteSubpart(), substituteMarker(), substituteMarkerInObject(), TEMPLATE() + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null) { - $timeTracker = $this->getTimeTracker(); - $timeTracker->push('substituteMarkerArrayCached'); - // If not arrays then set them - if (is_null($markContentArray)) { - // Plain markers - $markContentArray = []; - } - if (is_null($subpartContentArray)) { - // Subparts being directly substituted - $subpartContentArray = []; - } - if (is_null($wrappedSubpartContentArray)) { - // Subparts being wrapped - $wrappedSubpartContentArray = []; - } - // Finding keys and check hash: - $sPkeys = array_keys($subpartContentArray); - $wPkeys = array_keys($wrappedSubpartContentArray); - $keysToReplace = array_merge(array_keys($markContentArray), $sPkeys, $wPkeys); - if (empty($keysToReplace)) { - $timeTracker->pull(); - return $content; - } - asort($keysToReplace); - $storeKey = md5('substituteMarkerArrayCached_storeKey:' . serialize([$content, $keysToReplace])); - if ($this->substMarkerCache[$storeKey]) { - $storeArr = $this->substMarkerCache[$storeKey]; - $timeTracker->setTSlogMessage('Cached', 0); - } else { - $cache = $this->getCache(); - $storeArrDat = $cache->get($storeKey); - if (is_array($storeArrDat)) { - $storeArr = $storeArrDat; - // Setting cache: - $this->substMarkerCache[$storeKey] = $storeArr; - $timeTracker->setTSlogMessage('Cached from DB', 0); - } else { - // Finding subparts and substituting them with the subpart as a marker - foreach ($sPkeys as $sPK) { - $content = $this->substituteSubpart($content, $sPK, $sPK); - } - // Finding subparts and wrapping them with markers - foreach ($wPkeys as $wPK) { - $content = $this->substituteSubpart($content, $wPK, [ - $wPK, - $wPK - ]); - } - - $storeArr = []; - // search all markers in the content - $result = preg_match_all('/###([^#](?:[^#]*+|#{1,2}[^#])+)###/', $content, $markersInContent); - if ($result !== false && !empty($markersInContent[1])) { - $keysToReplaceFlipped = array_flip($keysToReplace); - $regexKeys = []; - $wrappedKeys = []; - // Traverse keys and quote them for reg ex. - foreach ($markersInContent[1] as $key) { - if (isset($keysToReplaceFlipped['###' . $key . '###'])) { - $regexKeys[] = preg_quote($key, '/'); - $wrappedKeys[] = '###' . $key . '###'; - } - } - $regex = '/###(?:' . implode('|', $regexKeys) . ')###/'; - $storeArr['c'] = preg_split($regex, $content); // contains all content parts around markers - $storeArr['k'] = $wrappedKeys; // contains all markers incl. ### - // Setting cache: - $this->substMarkerCache[$storeKey] = $storeArr; - // Storing the cached data - $cache->set($storeKey, $storeArr, ['substMarkArrayCached'], 0); - } - $timeTracker->setTSlogMessage('Parsing', 0); - } - } - if (!empty($storeArr['k']) && is_array($storeArr['k'])) { - // Substitution/Merging: - // Merging content types together, resetting - $valueArr = array_merge($markContentArray, $subpartContentArray, $wrappedSubpartContentArray); - $wSCA_reg = []; - $content = ''; - // Traversing the keyList array and merging the static and dynamic content - foreach ($storeArr['k'] as $n => $keyN) { - // add content before marker - $content .= $storeArr['c'][$n]; - if (!is_array($valueArr[$keyN])) { - // fetch marker replacement from $markContentArray or $subpartContentArray - $content .= $valueArr[$keyN]; - } else { - if (!isset($wSCA_reg[$keyN])) { - $wSCA_reg[$keyN] = 0; - } - // fetch marker replacement from $wrappedSubpartContentArray - $content .= $valueArr[$keyN][$wSCA_reg[$keyN] % 2]; - $wSCA_reg[$keyN]++; - } - } - // add remaining content - $content .= $storeArr['c'][count($storeArr['k'])]; - } - $timeTracker->pull(); - return $content; + GeneralUtility::logDeprecatedFunction(); + return $this->templateService->substituteMarkerArrayCached($content, $markContentArray, $subpartContentArray, $wrappedSubpartContentArray); } /** @@ -1756,9 +1659,11 @@ class ContentObjectRenderer * @param bool $deleteUnused If set, all unused marker are deleted. * @return string The processed output stream * @see substituteMarker(), substituteMarkerInObject(), TEMPLATE() + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteMarkerArray($content, array $markContentArray, $wrap = '', $uppercase = false, $deleteUnused = false) { + GeneralUtility::logDeprecatedFunction(); return $this->templateService->substituteMarkerArray($content, $markContentArray, $wrap, $uppercase, $deleteUnused); } @@ -1769,15 +1674,17 @@ class ContentObjectRenderer * @param array $markContentArray The array of key/value pairs being marker/content values used in the substitution. For each element in this array the function will substitute a marker in the content string/array values. * @return mixed The processed input variable. * @see substituteMarker() + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteMarkerInObject(&$tree, array $markContentArray) { + GeneralUtility::logDeprecatedFunction(); if (is_array($tree)) { foreach ($tree as $key => $value) { - $this->substituteMarkerInObject($tree[$key], $markContentArray); + $this->templateService->substituteMarkerInObject($tree[$key], $markContentArray); } } else { - $tree = $this->substituteMarkerArray($tree, $markContentArray); + $tree = $this->templateService->substituteMarkerArray($tree, $markContentArray); } return $tree; } @@ -1791,9 +1698,11 @@ class ContentObjectRenderer * @param bool $uppercase * @param bool $deleteUnused * @return string + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function substituteMarkerAndSubpartArrayRecursive($content, array $markersAndSubparts, $wrap = '', $uppercase = false, $deleteUnused = false) { + GeneralUtility::logDeprecatedFunction(); return $this->templateService->substituteMarkerAndSubpartArrayRecursive($content, $markersAndSubparts, $wrap, $uppercase, $deleteUnused); } @@ -1808,28 +1717,13 @@ class ContentObjectRenderer * @param string $prefix Prefix string to the fieldname before it is added as a key in the $markContentArray. Notice that the keys added to the $markContentArray always start and end with "### * @param bool $HSC If set, all values are passed through htmlspecialchars() - RECOMMENDED to avoid most obvious XSS and maintain XHTML compliance. * @return array The modified $markContentArray + * @deprecated since TYPO3 v8, will be removed in TYPO3 v9, please use the MarkerBasedTemplateService instead. */ public function fillInMarkerArray(array $markContentArray, array $row, $fieldList = '', $nl2br = true, $prefix = 'FIELD_', $HSC = false) { + GeneralUtility::logDeprecatedFunction(); $tsfe = $this->getTypoScriptFrontendController(); - if ($fieldList) { - $fArr = GeneralUtility::trimExplode(',', $fieldList, true); - foreach ($fArr as $field) { - $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($row[$field], !empty($tsfe->xhtmlDoctype)) : $row[$field]; - } - } else { - if (is_array($row)) { - foreach ($row as $field => $value) { - if (!MathUtility::canBeInterpretedAsInteger($field)) { - if ($HSC) { - $value = htmlspecialchars($value); - } - $markContentArray['###' . $prefix . $field . '###'] = $nl2br ? nl2br($value, !empty($tsfe->xhtmlDoctype)) : $value; - } - } - } - } - return $markContentArray; + return $this->templateService->fillInMarkerArray($markContentArray, $row, $fieldList, $nl2br, $prefix, $HSC, !empty($tsfe->xhtmlDoctype)); } /** @@ -8373,12 +8267,4 @@ class ContentObjectRenderer { return $this->typoScriptFrontendController ?: $GLOBALS['TSFE']; } - - /** - * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface - */ - protected function getCache() - { - return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash'); - } } diff --git a/typo3/sysext/frontend/Classes/ContentObject/TemplateContentObject.php b/typo3/sysext/frontend/Classes/ContentObject/TemplateContentObject.php index 2a6ccba2c7392a0b4619aeeedc3ba5f9dfe88102..40f2944d794c0c2f97c723552b7b3f19e20a7a5c 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/TemplateContentObject.php +++ b/typo3/sysext/frontend/Classes/ContentObject/TemplateContentObject.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Frontend\ContentObject; */ use TYPO3\CMS\Core\Html\HtmlParser; +use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -22,6 +23,22 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; */ class TemplateContentObject extends AbstractContentObject { + /** + * @var MarkerBasedTemplateService + */ + protected $templateService; + + /** + * Default constructor, which also instantiates the MarkerBasedTemplateService. + * + * @param ContentObjectRenderer $cObj + */ + public function __construct(ContentObjectRenderer $cObj) + { + $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); + parent::__construct($cObj); + } + /** * Rendering the cObject, TEMPLATE * @@ -45,7 +62,7 @@ class TemplateContentObject extends AbstractContentObject $content = $this->cObj->cObjGetSingle($conf['template'], $conf['template.'], 'template'); $workOnSubpart = isset($conf['workOnSubpart.']) ? $this->cObj->stdWrap($conf['workOnSubpart'], $conf['workOnSubpart.']) : $conf['workOnSubpart']; if ($workOnSubpart) { - $content = $this->cObj->getSubpart($content, $PRE . $workOnSubpart . $POST); + $content = $this->templateService->getSubpart($content, $PRE . $workOnSubpart . $POST); } // Fixing all relative paths found: if ($conf['relPathPrefix']) { @@ -68,10 +85,10 @@ class TemplateContentObject extends AbstractContentObject if (is_array($conf['subparts.'])) { foreach ($conf['subparts.'] as $theKey => $theValue) { if (!strstr($theKey, '.')) { - $subpart = $this->cObj->getSubpart($content, $PRE . $theKey . $POST); + $subpart = $this->templateService->getSubpart($content, $PRE . $theKey . $POST); if ($subpart) { $this->cObj->setCurrentVal($subpart); - $content = $this->cObj->substituteSubpart($content, $PRE . $theKey . $POST, $this->cObj->cObjGetSingle($theValue, $conf['subparts.'][$theKey . '.'], 'subparts.' . $theKey), true); + $content = $this->templateService->substituteSubpart($content, $PRE . $theKey . $POST, $this->cObj->cObjGetSingle($theValue, $conf['subparts.'][$theKey . '.'], 'subparts.' . $theKey), true); } } } @@ -80,10 +97,10 @@ class TemplateContentObject extends AbstractContentObject if (is_array($conf['wraps.'])) { foreach ($conf['wraps.'] as $theKey => $theValue) { if (!strstr($theKey, '.')) { - $subpart = $this->cObj->getSubpart($content, $PRE . $theKey . $POST); + $subpart = $this->templateService->getSubpart($content, $PRE . $theKey . $POST); if ($subpart) { $this->cObj->setCurrentVal($subpart); - $content = $this->cObj->substituteSubpart($content, $PRE . $theKey . $POST, explode('|', $this->cObj->cObjGetSingle($theValue, $conf['wraps.'][$theKey . '.'], 'wraps.' . $theKey)), true); + $content = $this->templateService->substituteSubpart($content, $PRE . $theKey . $POST, explode('|', $this->cObj->cObjGetSingle($theValue, $conf['wraps.'][$theKey . '.'], 'wraps.' . $theKey)), true); } } } @@ -94,7 +111,7 @@ class TemplateContentObject extends AbstractContentObject if (is_array($conf['subparts.'])) { foreach ($conf['subparts.'] as $theKey => $theValue) { if (!strstr($theKey, '.')) { - $subpart = $this->cObj->getSubpart($content, $PRE . $theKey . $POST); + $subpart = $this->templateService->getSubpart($content, $PRE . $theKey . $POST); if ($subpart) { $GLOBALS['TSFE']->register['SUBPART_' . $theKey] = $subpart; $subparts[$theKey]['name'] = $theValue; @@ -144,10 +161,10 @@ class TemplateContentObject extends AbstractContentObject // Substitution $substMarksSeparately = isset($conf['substMarksSeparately.']) ? $this->cObj->stdWrap($conf['substMarksSeparately'], $conf['substMarksSeparately.']) : $conf['substMarksSeparately']; if ($substMarksSeparately) { - $content = $this->cObj->substituteMarkerArrayCached($content, [], $subpartArray, $subpartWraps); - $content = $this->cObj->substituteMarkerArray($content, $markerArray); + $content = $this->templateService->substituteMarkerArrayCached($content, [], $subpartArray, $subpartWraps); + $content = $this->templateService->substituteMarkerArray($content, $markerArray); } else { - $content = $this->cObj->substituteMarkerArrayCached($content, $markerArray, $subpartArray, $subpartWraps); + $content = $this->templateService->substituteMarkerArrayCached($content, $markerArray, $subpartArray, $subpartWraps); } } } diff --git a/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php b/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php index 53661a0074c697ee77d10a1c6f49694f4b1c14b4..b0b50e0b2f9134ae30b719eceeda3187861c8833 100644 --- a/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php +++ b/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php @@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Database\Query\QueryHelper; use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer; use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Localization\LocalizationFactory; +use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -234,6 +235,11 @@ class AbstractPlugin */ protected $databaseConnection; + /** + * @var MarkerBasedTemplateService + */ + protected $templateService; + /** * Class Constructor (true constructor) * Initializes $this->piVars if $this->prefixId is set to any value @@ -246,6 +252,7 @@ class AbstractPlugin { $this->databaseConnection = $databaseConnection ?: $GLOBALS['TYPO3_DB']; $this->frontendController = $frontendController ?: $GLOBALS['TSFE']; + $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); // Setting piVars: if ($this->prefixId) { $this->piVars = GeneralUtility::_GPmerged($this->prefixId); @@ -675,7 +682,7 @@ class AbstractPlugin $markerArray['###CURRENT_PAGE###'] = $this->cObj->wrap($pointer + 1, $wrapper['showResultsNumbersWrap']); $markerArray['###TOTAL_PAGES###'] = $this->cObj->wrap($totalPages, $wrapper['showResultsNumbersWrap']); // Substitute markers - $resultCountMsg = $this->cObj->substituteMarkerArray($this->pi_getLL('pi_list_browseresults_displays', 'Displaying results ###FROM### to ###TO### out of ###OUT_OF###'), $markerArray); + $resultCountMsg = $this->templateService->substituteMarkerArray($this->pi_getLL('pi_list_browseresults_displays', 'Displaying results ###FROM### to ###TO### out of ###OUT_OF###'), $markerArray); } else { // Render the resultcount in the "traditional" way using sprintf $resultCountMsg = sprintf(str_replace('###SPAN_BEGIN###', '<span' . $this->pi_classParam('browsebox-strong') . '>', $this->pi_getLL('pi_list_browseresults_displays', 'Displaying results ###SPAN_BEGIN###%s to %s</span> out of ###SPAN_BEGIN###%s</span>')), $count > 0 ? $pR1 : 0, min($count, $pR2), $count);