[FEATURE] Add Contexts for storing data access modes
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / ContentObjectRenderer.php
index 779fef1..e80cd19 100644 (file)
@@ -16,7 +16,11 @@ namespace TYPO3\CMS\Frontend\ContentObject;
 
 use Doctrine\DBAL\DBALException;
 use Doctrine\DBAL\Driver\Statement;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
@@ -28,7 +32,6 @@ use TYPO3\CMS\Core\Html\HtmlParser;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
-use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Mail\MailMessage;
 use TYPO3\CMS\Core\Resource\Exception;
 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
@@ -73,8 +76,10 @@ use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
  * The class is normally instantiated and referred to as "cObj".
  * When you call your own PHP-code typically through a USER or USER_INT cObject then it is this class that instantiates the object and calls the main method. Before it does so it will set (if you are using classes) a reference to itself in the internal variable "cObj" of the object. Thus you can access all functions and data from this class by $this->cObj->... from within you classes written to be USER or USER_INT content objects.
  */
-class ContentObjectRenderer
+class ContentObjectRenderer implements LoggerAwareInterface
 {
+    use LoggerAwareTrait;
+
     /**
      * @var array
      */
@@ -400,7 +405,7 @@ class ContentObjectRenderer
     /**
      * @var File Current file objects (during iterations over files)
      */
-    protected $currentFile = null;
+    protected $currentFile;
 
     /**
      * Set to TRUE by doConvertToUserIntObject() if USER object wants to become USER_INT
@@ -788,7 +793,7 @@ class ContentObjectRenderer
         $content = '';
 
         // Evaluate possible cache and return
-        $cacheConfiguration = isset($configuration['cache.']) ? $configuration['cache.'] : null;
+        $cacheConfiguration = $configuration['cache.'] ?? null;
         if ($cacheConfiguration !== null) {
             unset($configuration['cache.']);
             $cache = $this->getFromCache($cacheConfiguration);
@@ -1035,7 +1040,7 @@ class ContentObjectRenderer
         if (!is_array($info)) {
             return '';
         }
-        if (is_file(PATH_site . $info['3'])) {
+        if (is_file(Environment::getPublicPath() . '/' . $info['3'])) {
             $source = $tsfe->absRefPrefix . str_replace('%2F', '/', rawurlencode($info['3']));
         } else {
             $source = $info[3];
@@ -1061,7 +1066,7 @@ class ContentObjectRenderer
             'altParams' => $altParam,
             'border' =>  $this->getBorderAttr(' border="' . (int)$conf['border'] . '"'),
             'sourceCollection' => $sourceCollection,
-            'selfClosingTagSlash' => (!empty($tsfe->xhtmlDoctype) ? ' /' : ''),
+            'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '',
         ];
 
         $theValue = $this->templateService->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true);
@@ -1158,7 +1163,7 @@ class ContentObjectRenderer
                 ];
 
                 if (isset($sourceConfiguration['quality']) || isset($sourceConfiguration['quality.'])) {
-                    $imageQuality = isset($sourceConfiguration['quality']) ? $sourceConfiguration['quality'] : '';
+                    $imageQuality = $sourceConfiguration['quality'] ?? '';
                     if (isset($sourceConfiguration['quality.'])) {
                         $imageQuality = $this->stdWrap($sourceConfiguration['quality'], $sourceConfiguration['quality.']);
                     }
@@ -1448,7 +1453,7 @@ class ContentObjectRenderer
             'aTagParams' => &$aTagParams
         ];
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getATagParamsPostProc'] ?? [] as $className) {
-            $processor =& GeneralUtility::makeInstance($className);
+            $processor = & GeneralUtility::makeInstance($className);
             $aTagParams = $processor->process($_params, $this);
         }
 
@@ -1875,7 +1880,7 @@ class ContentObjectRenderer
      */
     public function stdWrap_ifNull($content = '', $conf = [])
     {
-        return $content !== null ? $content : $conf['ifNull'];
+        return $content ?? $conf['ifNull'];
     }
 
     /**
@@ -2821,7 +2826,7 @@ class ContentObjectRenderer
      */
     public function stdWrap_editIcons($content = '', $conf = [])
     {
-        if ($this->getTypoScriptFrontendController()->beUserLogin && $conf['editIcons']) {
+        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $conf['editIcons']) {
             if (!is_array($conf['editIcons.'])) {
                 $conf['editIcons.'] = [];
             }
@@ -2840,7 +2845,7 @@ class ContentObjectRenderer
      */
     public function stdWrap_editPanel($content = '', $conf = [])
     {
-        if ($this->getTypoScriptFrontendController()->beUserLogin) {
+        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
             $content = $this->editPanel($content, $conf['editPanel.']);
         }
         return $content;
@@ -3248,9 +3253,9 @@ class ContentObjectRenderer
         $comment = htmlspecialchars($this->insertData($parts[1]));
         $output = LF
             . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [begin] -->' . LF
-            . str_pad('', ($indent + 1), TAB) . $content . LF
+            . str_pad('', $indent + 1, TAB) . $content . LF
             . str_pad('', $indent, TAB) . '<!-- ' . $comment . ' [end] -->' . LF
-            . str_pad('', ($indent + 1), TAB);
+            . str_pad('', $indent + 1, TAB);
         return $output;
     }
 
@@ -3422,13 +3427,13 @@ class ContentObjectRenderer
                     continue;
                 }
                 preg_match($chars < 0 ? $closingTagRegEx : $openingTagRegEx, $splittedContent[$offset], $matches);
-                $tagName = isset($matches[1]) ? $matches[1] : null;
+                $tagName = $matches[1] ?? null;
                 if ($tagName !== null) {
                     // Seek for the closing (or opening) tag.
                     $countSplittedContent = count($splittedContent);
                     for ($seekingOffset = $offset + 2; $seekingOffset < $countSplittedContent; $seekingOffset = $seekingOffset + 2) {
                         preg_match($chars < 0 ? $openingTagRegEx : $closingTagRegEx, $splittedContent[$seekingOffset], $matches);
-                        $seekingTagName = isset($matches[1]) ? $matches[1] : null;
+                        $seekingTagName = $matches[1] ?? null;
                         if ($tagName === $seekingTagName) {
                             // We found a matching tag.
                             // Add closing tag only if it occurs after the cropped content item.
@@ -3555,7 +3560,7 @@ class ContentObjectRenderer
                 ? $this->stdWrap($conf['icon.']['ext'], $conf['icon.']['ext.'])
                 : $conf['icon.']['ext'];
             $iconExt = !empty($conf['icon.']['ext']) ? '.' . $conf['icon.']['ext'] : '.gif';
-            $icon = @is_file(($iconPath . $fI['fileext'] . $iconExt))
+            $icon = @is_file($iconPath . $fI['fileext'] . $iconExt)
                 ? $iconPath . $fI['fileext'] . $iconExt
                 : $iconPath . 'default' . $iconExt;
             $icon = PathUtility::stripPathSitePrefix($icon);
@@ -3736,7 +3741,7 @@ class ContentObjectRenderer
         // return value directly by returnKey. No further processing
         if (!empty($valArr) && (MathUtility::canBeInterpretedAsInteger($conf['returnKey']) || $conf['returnKey.'])) {
             $key = isset($conf['returnKey.']) ? (int)$this->stdWrap($conf['returnKey'], $conf['returnKey.']) : (int)$conf['returnKey'];
-            return isset($valArr[$key]) ? $valArr[$key] : '';
+            return $valArr[$key] ?? '';
         }
 
         // return the amount of elements. No further processing
@@ -3835,7 +3840,7 @@ class ContentObjectRenderer
                     $modifiers = substr($search, $startModifiers + 1);
                     // remove "e" (eval-modifier), which would otherwise allow to run arbitrary PHP-code
                     $modifiers = str_replace('e', '', $modifiers);
-                    $search = substr($search, 0, ($startModifiers + 1)) . $modifiers;
+                    $search = substr($search, 0, $startModifiers + 1) . $modifiers;
                 }
                 if (empty($useOptionSplitReplace)) {
                     $content = preg_replace($search, $replace, $content);
@@ -4361,8 +4366,9 @@ class ContentObjectRenderer
                     $attrib['align'] = $defaultAlign;
                 }
                 $params = GeneralUtility::implodeAttributes($attrib, true);
-                if (!($conf['removeWrapping'] && !($emptyTag && $conf['removeWrapping.']['keepSingleTag']))) {
-                    if ($emptyTag) {
+                if (!$conf['removeWrapping'] || ($emptyTag && $conf['removeWrapping.']['keepSingleTag'])) {
+                    $selfClosingTagList = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
+                    if ($emptyTag && in_array(strtolower($uTagName), $selfClosingTagList, true)) {
                         $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . ' />';
                     } else {
                         $str_content = '<' . strtolower($uTagName) . (trim($params) ? ' ' . trim($params) : '') . '>' . $str_content . '</' . strtolower($uTagName) . '>';
@@ -4391,7 +4397,7 @@ class ContentObjectRenderer
     {
         $aTagParams = $this->getATagParams($conf);
         $textstr = '';
-        foreach ([ 'http://', 'https://' ] as $scheme) {
+        foreach (['http://', 'https://'] as $scheme) {
             $textpieces = explode($scheme, $data);
             $pieces = count($textpieces);
             $textstr = $textpieces[0];
@@ -4436,7 +4442,7 @@ class ContentObjectRenderer
 
                     $res = '<a href="' . htmlspecialchars($linkUrl) . '"'
                         . ($target !== '' ? ' target="' . htmlspecialchars($target) . '"' : '')
-                        . $aTagParams . $this->extLinkATagParams(('http://' . $parts[0]), 'url') . '>';
+                        . $aTagParams . $this->extLinkATagParams('http://' . $parts[0], 'url') . '>';
 
                     $wrap = isset($conf['wrap.']) ? $this->stdWrap($conf['wrap'], $conf['wrap.']) : $conf['wrap'];
                     if ((string)$conf['ATagBeforeWrap'] !== '') {
@@ -4574,9 +4580,7 @@ class ContentObjectRenderer
                         $fileObject = $this->getResourceFactory()->retrieveFileOrFolderObject($file);
                     }
                 } catch (Exception $exception) {
-                    /** @var \TYPO3\CMS\Core\Log\Logger $logger */
-                    $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
-                    $logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
+                    $this->logger->warning('The image "' . $file . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
                     return null;
                 }
             }
@@ -4620,10 +4624,8 @@ class ContentObjectRenderer
                         $processingConfiguration['maskImages']['maskBottomImageMask'] = $bottomImg_mask['processedFile'];
                     }
                     $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
-                    $hash = $processedFileObject->calculateChecksum();
-                    // store info in the TSFE template cache (kept for backwards compatibility)
-                    if ($processedFileObject->isProcessed() && !isset($tsfe->tmpl->fileCache[$hash])) {
-                        $tsfe->tmpl->fileCache[$hash] = [
+                    if ($processedFileObject->isProcessed()) {
+                        $imageResource = [
                             0 => $processedFileObject->getProperty('width'),
                             1 => $processedFileObject->getProperty('height'),
                             2 => $processedFileObject->getExtension(),
@@ -4633,11 +4635,9 @@ class ContentObjectRenderer
                             // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder,
                             // in order for the setup-array to create a unique filename hash.
                             'originalFile' => $fileObject,
-                            'processedFile' => $processedFileObject,
-                            'fileCacheHash' => $hash
+                            'processedFile' => $processedFileObject
                         ];
                     }
-                    $imageResource = $tsfe->tmpl->fileCache[$hash];
                 }
             }
         }
@@ -4891,7 +4891,7 @@ class ContentObjectRenderer
                             $rootLine = $tsfe->rootLine;
                             array_shift($rootLine);
                             foreach ($rootLine as $rootLinePage) {
-                                $retVal = (string) $rootLinePage['backend_layout_next_level'];
+                                $retVal = (string)$rootLinePage['backend_layout_next_level'];
                                 // If layout for "next level" is set to "none" - don't use any and stop searching
                                 if ($retVal === '-1') {
                                     $retVal = 'none';
@@ -5016,9 +5016,7 @@ class ContentObjectRenderer
                 $fileObject = null;
             }
         } catch (Exception $exception) {
-            /** @var \TYPO3\CMS\Core\Log\Logger $logger */
-            $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
-            $logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
+            $this->logger->warning('The file "' . $fileUidOrCurrentKeyword . '" could not be found and won\'t be included in frontend output', ['exception' => $exception]);
             $fileObject = null;
         }
 
@@ -5255,7 +5253,13 @@ class ContentObjectRenderer
 
         // Detecting kind of link and resolve all necessary parameters
         $linkService = GeneralUtility::makeInstance(LinkService::class);
-        $linkDetails = $linkService->resolve($linkParameter);
+        try {
+            $linkDetails = $linkService->resolve($linkParameter);
+        } catch (Exception\InvalidPathException $exception) {
+            $this->logger->warning('The link could not be generated', ['exception' => $exception]);
+            return $linkText;
+        }
+
         $linkDetails['typoLinkParameter'] = $linkParameter;
         if (isset($linkDetails['type']) && isset($GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
             /** @var AbstractTypolinkBuilder $linkBuilder */
@@ -5266,6 +5270,8 @@ class ContentObjectRenderer
             try {
                 list($this->lastTypoLinkUrl, $linkText, $target) = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
             } catch (UnableToLinkException $e) {
+                $this->logger->debug(sprintf('Unable to link "%s": %s', $e->getLinkText(), $e->getMessage()), ['exception' => $e]);
+
                 // Only return the link text directly
                 return $e->getLinkText();
             }
@@ -5553,7 +5559,7 @@ class ContentObjectRenderer
      *
      * @param string $mailAddress Email address
      * @param string $linktxt Link text, default will be the email address.
-     * @return string Returns a numerical array with two elements: 1) $mailToUrl, string ready to be inserted into the href attribute of the <a> tag, b) $linktxt: The string between starting and ending <a> tag.
+     * @return array A numerical array with two elements: 1) $mailToUrl, string ready to be inserted into the href attribute of the <a> tag, b) $linktxt: The string between starting and ending <a> tag.
      */
     public function getMailTo($mailAddress, $linktxt)
     {
@@ -5724,11 +5730,7 @@ class ContentObjectRenderer
         } else {
             $newQueryArray = $currentQueryArray;
         }
-        if ($forceOverruleArguments) {
-            ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments);
-        } else {
-            ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, false);
-        }
+        ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, $forceOverruleArguments);
         return GeneralUtility::implodeArrayForUrl('', $newQueryArray, '', false, true);
     }
 
@@ -6046,9 +6048,7 @@ class ContentObjectRenderer
      */
     public function enableFields($table, $show_hidden = false, array $ignore_array = [])
     {
-        $tsfe = $this->getTypoScriptFrontendController();
-        $show_hidden = $show_hidden ?: ($table === 'pages' ? $tsfe->showHiddenPage : $tsfe->showHiddenRecords);
-        return $tsfe->sys_page->enableFields($table, (bool)$show_hidden, $ignore_array);
+        return $this->getTypoScriptFrontendController()->sys_page->enableFields($table, $show_hidden ? true : -1, $ignore_array);
     }
 
     /**
@@ -6105,7 +6105,7 @@ class ContentObjectRenderer
                 $addSelectFields,
                 $moreWhereClauses,
                 $prevId_array,
-                $tsfe->gr_list
+                GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])
             ];
             $requestHash = md5(serialize($parameters));
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
@@ -6323,10 +6323,7 @@ class ContentObjectRenderer
             $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
             foreach ($searchFields as $field) {
                 $searchWordConstraint->add(
-                    $queryBuilder->expr()->like(
-                        $prefixTableName . $field,
-                        $queryBuilder->createNamedParameter('%' . $searchWord . '%', \PDO::PARAM_STR)
-                    )
+                    $queryBuilder->expr()->like($prefixTableName . $field, $queryBuilder->quote('%' . $searchWord . '%'))
                 );
             }
 
@@ -6715,7 +6712,7 @@ class ContentObjectRenderer
         ];
 
         $considerMovePlaceholders = (
-            $tsfe->sys_page->versioningPreview && $table !== 'pages'
+            $tsfe->sys_page->versioningWorkspaceId > 0 && $table !== 'pages'
             && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
         );
 
@@ -6796,8 +6793,7 @@ class ContentObjectRenderer
                 // Use this option to include records that don't have a default translation
                 // (originalpointerfield is 0 and the language field contains the requested language)
                 $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
-                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) :
-                    $conf['includeRecordsWithoutDefaultTranslation'];
+                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) : $conf['includeRecordsWithoutDefaultTranslation'];
                 if (trim($includeRecordsWithoutDefaultTranslation) !== '') {
                     $languageQuery = $expressionBuilder->orX(
                         $languageQuery,
@@ -6867,7 +6863,7 @@ class ContentObjectRenderer
         $matchEnd = '(\\s*,|\\s*$)/';
         $necessaryFields = ['uid', 'pid'];
         $wsFields = ['t3ver_state'];
-        if (isset($GLOBALS['TCA'][$table]) && !preg_match(($matchStart . '\\*' . $matchEnd), $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)/i', $selectPart)) {
+        if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)/i', $selectPart)) {
             foreach ($necessaryFields as $field) {
                 $match = $matchStart . $field . $matchEnd;
                 if (!preg_match($match, $selectPart)) {
@@ -7002,7 +6998,7 @@ class ContentObjectRenderer
                     // Handle float
                     $markerValues[$marker] = (float)$tempValue;
                 }
-            } elseif (is_null($tempValue)) {
+            } elseif ($tempValue === null) {
                 // It represents NULL
                 $markerValues[$marker] = 'NULL';
             } elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
@@ -7060,7 +7056,7 @@ class ContentObjectRenderer
      */
     public function editPanel($content, $conf, $currentRecord = '', $dataArr = [])
     {
-        if ($this->getTypoScriptFrontendController()->beUserLogin && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
+        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
             if (!$currentRecord) {
                 $currentRecord = $this->currentRecord;
             }
@@ -7087,7 +7083,7 @@ class ContentObjectRenderer
      */
     public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArr = [], $addUrlParamStr = '')
     {
-        if ($this->getTypoScriptFrontendController()->beUserLogin && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
+        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
             if (!$currentRecord) {
                 $currentRecord = $this->currentRecord;
             }
@@ -7170,9 +7166,7 @@ class ContentObjectRenderer
      */
     protected function calculateCacheLifetime(array $configuration)
     {
-        $lifetimeConfiguration = isset($configuration['lifetime'])
-            ? $configuration['lifetime']
-            : '';
+        $lifetimeConfiguration = $configuration['lifetime'] ?? '';
         $lifetimeConfiguration = isset($configuration['lifetime.'])
             ? $this->stdWrap($lifetimeConfiguration, $configuration['lifetime.'])
             : $lifetimeConfiguration;
@@ -7194,7 +7188,7 @@ class ContentObjectRenderer
      */
     protected function calculateCacheTags(array $configuration)
     {
-        $tags = isset($configuration['tags']) ? $configuration['tags'] : '';
+        $tags = $configuration['tags'] ?? '';
         $tags = isset($configuration['tags.'])
             ? $this->stdWrap($tags, $configuration['tags.'])
             : $tags;
@@ -7209,7 +7203,7 @@ class ContentObjectRenderer
      */
     protected function calculateCacheKey(array $configuration)
     {
-        $key = isset($configuration['key']) ? $configuration['key'] : '';
+        $key = $configuration['key'] ?? '';
         return isset($configuration['key.'])
             ? $this->stdWrap($key, $configuration['key.'])
             : $key;