[BUGFIX] Include Records with "All Languages" in default language
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / ContentObject / ContentObjectRenderer.php
index be6346c..677625b 100644 (file)
@@ -24,11 +24,11 @@ use TYPO3\CMS\Core\Context\LanguageAspect;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
-use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
 use TYPO3\CMS\Core\Html\HtmlParser;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
@@ -43,19 +43,21 @@ use TYPO3\CMS\Core\Resource\FileReference;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
-use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
 use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\DebugUtility;
+use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Utility\MailUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
@@ -118,8 +120,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
         'cObject' => 'cObject',
         'cObject.' => 'array',
         'numRows.' => 'array',
-        'filelist' => 'dir',
-        'filelist.' => 'array',
         'preUserFunc' => 'functionName',
         'stdWrapOverride' => 'hook',
         // this is a placeholder for the second Hook
@@ -207,8 +207,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
         'innerWrap.' => 'array',
         'innerWrap2' => 'wrap',
         'innerWrap2.' => 'array',
-        'addParams.' => 'array',
-        'filelink.' => 'array',
         'preCObject' => 'cObject',
         'preCObject.' => 'array',
         'postCObject' => 'cObject',
@@ -349,9 +347,9 @@ class ContentObjectRenderer implements LoggerAwareInterface
     public $checkPid_cache = [];
 
     /**
-     * @var string
+     * @var string|int
      */
-    public $checkPid_badDoktypeList = '255';
+    public $checkPid_badDoktypeList = PageRepository::DOKTYPE_RECYCLER;
 
     /**
      * This will be set by typoLink() to the url of the most recent link created.
@@ -607,7 +605,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      *
      * @param array $data The record array
      * @param string $currentRecord This is set to the [table]:[uid] of the record delivered in the $data-array, if the cObjects CONTENT or RECORD is in operation. Note that $GLOBALS['TSFE']->currentRecord is set to an equal value but always indicating the latest record rendered.
-     * @access private
+     * @internal
      */
     public function setParent($data, $currentRecord)
     {
@@ -729,7 +727,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
                                 if (!$hookObject instanceof ContentObjectGetSingleHookInterface) {
                                     throw new \UnexpectedValueException('$hookObject must implement interface ' . ContentObjectGetSingleHookInterface::class, 1195043731);
                                 }
-                                /** @var $hookObject ContentObjectGetSingleHookInterface */
+                                /** @var ContentObjectGetSingleHookInterface $hookObject */
                                 $content .= $hookObject->getSingleContentObject($name, (array)$conf, $TSkey, $this);
                             }
                         } else {
@@ -821,7 +819,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         if ($cacheConfiguration !== null) {
             $key = $this->calculateCacheKey($cacheConfiguration);
             if (!empty($key)) {
-                /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
+                /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
                 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
                 $tags = $this->calculateCacheTags($cacheConfiguration);
                 $lifetime = $this->calculateCacheLifetime($cacheConfiguration);
@@ -995,7 +993,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
      * @param array $pidConf stdWrap array for the list
      * @return string A list of PIDs
-     * @access private
+     * @internal
      */
     public function getSlidePids($pidList, $pidConf)
     {
@@ -1029,7 +1027,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $file File TypoScript resource
      * @param array $conf TypoScript configuration properties
      * @return string <img> tag, (possibly wrapped in links and other HTML) if any image found.
-     * @access private
+     * @internal
      * @see IMAGE()
      */
     public function cImage($file, $conf)
@@ -1427,7 +1425,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
     {
         $altText = isset($conf['altText.']) ? trim($this->stdWrap($conf['altText'], $conf['altText.'])) : trim($conf['altText']);
         $titleText = isset($conf['titleText.']) ? trim($this->stdWrap($conf['titleText'], $conf['titleText.'])) : trim($conf['titleText']);
-        if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] != 'html5') {
+        if (isset($conf['longdescURL.']) && $this->getTypoScriptFrontendController()->config['config']['doctype'] !== 'html5') {
             $longDescUrl = $this->typoLink_URL($conf['longdescURL.']);
         } else {
             $longDescUrl = trim($conf['longdescURL']);
@@ -1744,14 +1742,16 @@ class ContentObjectRenderer implements LoggerAwareInterface
      */
     public function stdWrap_lang($content = '', $conf = [])
     {
-        $tsfe = $this->getTypoScriptFrontendController();
-        if (
-            isset($conf['lang.'])
-            && isset($tsfe->config['config']['language'])
-            && $tsfe->config['config']['language']
-            && isset($conf['lang.'][$tsfe->config['config']['language']])
-        ) {
-            $content = $conf['lang.'][$tsfe->config['config']['language']];
+        $request = $GLOBALS['TYPO3_REQUEST'] ?? null;
+        $siteLanguage = $request ? $request->getAttribute('language') : null;
+        if ($siteLanguage instanceof SiteLanguage) {
+            $currentLanguageCode = $siteLanguage->getTypo3Language();
+        } else {
+            $tsfe = $this->getTypoScriptFrontendController();
+            $currentLanguageCode = $tsfe->config['config']['language'] ?? null;
+        }
+        if ($currentLanguageCode && isset($conf['lang.'][$currentLanguageCode])) {
+            $content = $conf['lang.'][$currentLanguageCode];
         }
         return $content;
     }
@@ -1828,19 +1828,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
-     * filelist
-     * Will create a list of files based on some additional parameters
-     *
-     * @param string $content Input value undergoing processing in this function.
-     * @param array $conf stdWrap properties for filelist.
-     * @return string The processed input value
-     */
-    public function stdWrap_filelist($content = '', $conf = [])
-    {
-        return $this->filelist($conf['filelist']);
-    }
-
-    /**
      * preUserFunc
      * Will execute a user public function before the content will be modified by any other stdWrap function
      *
@@ -2560,33 +2547,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
-     * addParams
-     * Adds tag attributes to any content that is a tag
-     *
-     * @param string $content Input value undergoing processing in this function.
-     * @param array $conf stdWrap properties for addParams.
-     * @return string The processed input value
-     */
-    public function stdWrap_addParams($content = '', $conf = [])
-    {
-        return $this->addParams($content, $conf['addParams.'] ?? []);
-    }
-
-    /**
-     * filelink
-     * Used to make lists of links to files
-     * See wrap
-     *
-     * @param string $content Input value undergoing processing in this function.
-     * @param array $conf stdWrap properties for filelink.
-     * @return string The processed input value
-     */
-    public function stdWrap_filelink($content = '', $conf = [])
-    {
-        return $this->filelink($content, $conf['filelink.'] ?? []);
-    }
-
-    /**
      * preCObject
      * A content object that is prepended to the current content but between the innerWraps and the rest of the wraps
      *
@@ -2915,7 +2875,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         if (empty($key)) {
             return $content;
         }
-        /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
+        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
         $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_hash');
         $tags = $this->calculateCacheTags($conf['cache.']);
         $lifetime = $this->calculateCacheLifetime($conf['cache.']);
@@ -2998,7 +2958,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      *
      * @param array $conf TypoScript properties for the property (see link to "numRows")
      * @return int The number of rows found by the select
-     * @access private
+     * @internal
      * @see stdWrap()
      */
     public function numRows($conf)
@@ -3077,7 +3037,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         if ($flag) {
             $value = isset($conf['value.'])
                 ? trim($this->stdWrap($conf['value'] ?? '', $conf['value.']))
-                : (trim($conf['value'] ?? ''));
+                : trim($conf['value'] ?? '');
             if (isset($conf['isGreaterThan']) || isset($conf['isGreaterThan.'])) {
                 $number = isset($conf['isGreaterThan.']) ? trim($this->stdWrap($conf['isGreaterThan'], $conf['isGreaterThan.'])) : trim($conf['isGreaterThan']);
                 if ($number <= $value) {
@@ -3110,110 +3070,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
-     * Reads a directory for files and returns the filepaths in a string list separated by comma.
-     * Implements the stdWrap property "filelist"
-     *
-     * @param string $data The command which contains information about what files/directory listing to return. See the "filelist" property of stdWrap for details.
-     * @return string Comma list of files.
-     * @access private
-     * @see stdWrap()
-     */
-    public function filelist($data)
-    {
-        $data = trim($data);
-        if ($data === '') {
-            return '';
-        }
-        list($possiblePath, $ext_list, $sorting, $reverse, $useFullPath) = GeneralUtility::trimExplode('|', $data);
-        // read directory:
-        // MUST exist!
-        $path = '';
-        // proceeds if no '//', '..' or '\' is in the $theFile
-        if (GeneralUtility::validPathStr($possiblePath)) {
-            // Removes all dots, slashes and spaces after a path.
-            $possiblePath = preg_replace('/[\\/\\. ]*$/', '', $possiblePath);
-            if (!GeneralUtility::isAbsPath($possiblePath) && @is_dir($possiblePath)) {
-                // Now check if it matches one of the FAL storages
-                $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
-                $storages = $storageRepository->findAll();
-                foreach ($storages as $storage) {
-                    if ($storage->getDriverType() === 'Local' && $storage->isPublic() && $storage->isOnline()) {
-                        $folder = $storage->getPublicUrl($storage->getRootLevelFolder(), true);
-                        if (GeneralUtility::isFirstPartOfStr($possiblePath . '/', $folder)) {
-                            $path = $possiblePath;
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-        if (!$path) {
-            return '';
-        }
-        $items = [
-            'files' => [],
-            'sorting' => []
-        ];
-        $ext_list = strtolower(GeneralUtility::uniqueList($ext_list));
-        // Read dir:
-        $d = @dir($path);
-        if (is_object($d)) {
-            $count = 0;
-            while ($entry = $d->read()) {
-                if ($entry !== '.' && $entry !== '..') {
-                    // Because of odd PHP-error where <br />-tag is sometimes placed after a filename!!
-                    $wholePath = $path . '/' . $entry;
-                    if (file_exists($wholePath) && filetype($wholePath) === 'file') {
-                        $info = GeneralUtility::split_fileref($wholePath);
-                        if (!$ext_list || GeneralUtility::inList($ext_list, $info['fileext'])) {
-                            $items['files'][] = $info['file'];
-                            switch ($sorting) {
-                                case 'name':
-                                    $items['sorting'][] = strtolower($info['file']);
-                                    break;
-                                case 'size':
-                                    $items['sorting'][] = filesize($wholePath);
-                                    break;
-                                case 'ext':
-                                    $items['sorting'][] = $info['fileext'];
-                                    break;
-                                case 'date':
-                                    $items['sorting'][] = filectime($wholePath);
-                                    break;
-                                case 'mdate':
-                                    $items['sorting'][] = filemtime($wholePath);
-                                    break;
-                                default:
-                                    $items['sorting'][] = $count;
-                            }
-                            $count++;
-                        }
-                    }
-                }
-            }
-            $d->close();
-        }
-        // Sort if required
-        if (!empty($items['sorting'])) {
-            if (strtolower($reverse) !== 'r') {
-                asort($items['sorting']);
-            } else {
-                arsort($items['sorting']);
-            }
-        }
-        if (!empty($items['files'])) {
-            // Make list
-            reset($items['sorting']);
-            $list_arr = [];
-            foreach ($items['sorting'] as $key => $v) {
-                $list_arr[] = $useFullPath ? $path . '/' . $items['files'][$key] : $items['files'][$key];
-            }
-            return implode(',', $list_arr);
-        }
-        return '';
-    }
-
-    /**
      * Passes the input value, $theValue, to an instance of "\TYPO3\CMS\Core\Html\HtmlParser"
      * together with the TypoScript options which are first converted from a TS style array
      * to a set of arrays with options for the \TYPO3\CMS\Core\Html\HtmlParser class.
@@ -3315,7 +3171,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $content The string to perform the operation on
      * @param string $options The parameters to substring, given as a comma list of integers where the first and second number is passed as arg 1 and 2 to substr().
      * @return string The processed input value.
-     * @access private
+     * @internal
      * @see stdWrap()
      */
     public function substring($content, $options)
@@ -3333,7 +3189,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $content The string to perform the operation on
      * @param string $options The parameters splitted by "|": First parameter is the max number of chars of the string. Negative value means cropping from end of string. Second parameter is the pre/postfix string to apply if cropping occurs. Third parameter is a boolean value. If set then crop will be applied at nearest space.
      * @return string The processed input value.
-     * @access private
+     * @internal
      * @see stdWrap()
      */
     public function crop($content, $options)
@@ -3373,7 +3229,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $content The string to perform the operation on
      * @param string $options The parameters splitted by "|": First parameter is the max number of chars of the string. Negative value means cropping from end of string. Second parameter is the pre/postfix string to apply if cropping occurs. Third parameter is a boolean value. If set then crop will be applied at nearest space.
      * @return string The processed input value.
-     * @access private
+     * @internal
      * @see stdWrap()
      */
     public function cropHTML($content, $options)
@@ -3509,216 +3365,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
-     * Implements the TypoScript function "addParams"
-     *
-     * @param string $content The string with the HTML tag.
-     * @param array $conf The TypoScript configuration properties
-     * @return string The modified string
-     * @todo Make it XHTML compatible. Will not present "/>" endings of tags right now. Further getting the tagname might fail if it is not separated by a normal space from the attributes.
-     */
-    public function addParams($content, $conf)
-    {
-        // For XHTML compliance.
-        $lowerCaseAttributes = true;
-        if (!is_array($conf)) {
-            return $content;
-        }
-        $key = 1;
-        $parts = explode('<', $content);
-        if (isset($conf['_offset']) && (int)$conf['_offset']) {
-            $key = (int)$conf['_offset'] < 0 ? count($parts) + (int)$conf['_offset'] : (int)$conf['_offset'];
-        }
-        $subparts = explode('>', $parts[$key] ?? '');
-        if (trim($subparts[0])) {
-            // Get attributes and name
-            $attribs = GeneralUtility::get_tag_attributes('<' . $subparts[0] . '>');
-            list($tagName) = explode(' ', $subparts[0], 2);
-            // adds/overrides attributes
-            foreach ($conf as $pkey => $val) {
-                if (substr($pkey, -1) !== '.' && $pkey[0] !== '_') {
-                    $tmpVal = isset($conf[$pkey . '.']) ? $this->stdWrap($conf[$pkey], $conf[$pkey . '.']) : (string)$val;
-                    if ($lowerCaseAttributes) {
-                        $pkey = strtolower($pkey);
-                    }
-                    if ($tmpVal !== '') {
-                        $attribs[$pkey] = $tmpVal;
-                    }
-                }
-            }
-            // Re-assembles the tag and content
-            $subparts[0] = trim($tagName . ' ' . GeneralUtility::implodeAttributes($attribs));
-            $parts[$key] = implode('>', $subparts);
-            $content = implode('<', $parts);
-        }
-        return $content;
-    }
-
-    /**
-     * Creates a list of links to files.
-     * Implements the stdWrap property "filelink"
-     *
-     * @param string $theValue The filename to link to, possibly prefixed with $conf[path]
-     * @param array $conf TypoScript parameters for the TypoScript function ->filelink
-     * @return string The link to the file possibly with icons, thumbnails, size in bytes shown etc.
-     * @access private
-     * @see stdWrap()
-     */
-    public function filelink($theValue, $conf)
-    {
-        $conf['path'] = isset($conf['path.'])
-            ? $this->stdWrap($conf['path'] ?? '', $conf['path.'])
-            : ($conf['path'] ?? '');
-        $theFile = trim($conf['path']) . $theValue;
-        if (!@is_file($theFile)) {
-            return '';
-        }
-        $theFileEnc = str_replace('%2F', '/', rawurlencode($theFile));
-        $title = $conf['title'] ?? '';
-        if (isset($conf['title.'])) {
-            $title = $this->stdWrap($title, $conf['title.']);
-        }
-        $target = $conf['target'] ?? '';
-        if (isset($conf['target.'])) {
-            $target = $this->stdWrap($target, $conf['target.']);
-        }
-        $tsfe = $this->getTypoScriptFrontendController();
-
-        $typoLinkConf = [
-            'parameter' => $theFileEnc,
-            'fileTarget' => $target,
-            'title' => $title,
-            'ATagParams' => $this->getATagParams($conf)
-        ];
-
-        if (isset($conf['typolinkConfiguration.'])) {
-            $additionalTypoLinkConfiguration = $conf['typolinkConfiguration.'];
-            // We only allow additional configuration. This is why the generated conf overwrites the additional conf.
-            ArrayUtility::mergeRecursiveWithOverrule($additionalTypoLinkConfiguration, $typoLinkConf);
-            $typoLinkConf = $additionalTypoLinkConfiguration;
-        }
-
-        $theLinkWrap = $this->typoLink('|', $typoLinkConf);
-        $theSize = filesize($theFile);
-        $fI = GeneralUtility::split_fileref($theFile);
-        $icon = '';
-        if ($conf['icon'] ?? false) {
-            $conf['icon.']['path'] = isset($conf['icon.']['path.'])
-                ? $this->stdWrap($conf['icon.']['path'], $conf['icon.']['path.'])
-                : $conf['icon.']['path'];
-            $iconPath = !empty($conf['icon.']['path'])
-                ? $conf['icon.']['path']
-                : GeneralUtility::getFileAbsFileName('EXT:frontend/Resources/Public/Icons/FileIcons/');
-            $conf['icon.']['ext'] = isset($conf['icon.']['ext.'])
-                ? $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)
-                ? $iconPath . $fI['fileext'] . $iconExt
-                : $iconPath . 'default' . $iconExt;
-            $icon = PathUtility::stripPathSitePrefix($icon);
-            // Checking for images: If image, then return link to thumbnail.
-            $IEList = isset($conf['icon_image_ext_list.']) ? $this->stdWrap($conf['icon_image_ext_list'], $conf['icon_image_ext_list.']) : $conf['icon_image_ext_list'];
-            $image_ext_list = str_replace(' ', '', strtolower($IEList));
-            if ($fI['fileext'] && GeneralUtility::inList($image_ext_list, $fI['fileext'])) {
-                if ($conf['iconCObject']) {
-                    $icon = $this->cObjGetSingle($conf['iconCObject'], $conf['iconCObject.'], 'iconCObject');
-                } else {
-                    $notFoundThumb = GeneralUtility::getFileAbsFileName('EXT:core/Resources/Public/Images/NotFound.gif');
-                    $notFoundThumb = PathUtility::stripPathSitePrefix($notFoundThumb);
-                    $sizeParts = [64, 64];
-                    if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['thumbnails']) {
-                        // using the File Abstraction Layer to generate a preview image
-                        try {
-                            /** @var File $fileObject */
-                            $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($theFile);
-                            if ($fileObject->isMissing()) {
-                                $icon = $notFoundThumb;
-                            } else {
-                                $fileExtension = $fileObject->getExtension();
-                                if ($fileExtension === 'ttf' || GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileExtension)) {
-                                    if ($conf['icon_thumbSize'] || $conf['icon_thumbSize.']) {
-                                        $thumbSize = isset($conf['icon_thumbSize.']) ? $this->stdWrap($conf['icon_thumbSize'], $conf['icon_thumbSize.']) : $conf['icon_thumbSize'];
-                                        $sizeParts = explode('x', $thumbSize);
-                                    }
-                                    $icon = $fileObject->process(ProcessedFile::CONTEXT_IMAGEPREVIEW, [
-                                        'width' => $sizeParts[0],
-                                        'height' => $sizeParts[1]
-                                    ])->getPublicUrl(true);
-                                }
-                            }
-                        } catch (ResourceDoesNotExistException $exception) {
-                            $icon = $notFoundThumb;
-                        }
-                    } else {
-                        $icon = $notFoundThumb;
-                    }
-                    $urlPrefix = '';
-                    if (parse_url($icon, PHP_URL_HOST) === null) {
-                        $urlPrefix = $tsfe->absRefPrefix;
-                    }
-                    $icon = '<img src="' . htmlspecialchars($urlPrefix . $icon) . '"' .
-                        ' width="' . (int)$sizeParts[0] . '" height="' . (int)$sizeParts[1] . '" ' .
-                        $this->getBorderAttr(' border="0"') . '' . $this->getAltParam($conf) . ' />';
-                }
-            } else {
-                $conf['icon.']['widthAttribute'] = isset($conf['icon.']['widthAttribute.'])
-                    ? $this->stdWrap($conf['icon.']['widthAttribute'], $conf['icon.']['widthAttribute.'])
-                    : $conf['icon.']['widthAttribute'];
-                $iconWidth = !empty($conf['icon.']['widthAttribute']) ? $conf['icon.']['widthAttribute'] : 18;
-                $conf['icon.']['heightAttribute'] = isset($conf['icon.']['heightAttribute.'])
-                    ? $this->stdWrap($conf['icon.']['heightAttribute'], $conf['icon.']['heightAttribute.'])
-                    : $conf['icon.']['heightAttribute'];
-                $iconHeight = !empty($conf['icon.']['heightAttribute']) ? (int)$conf['icon.']['heightAttribute'] : 16;
-                $icon = '<img src="' . htmlspecialchars($tsfe->absRefPrefix . $icon) . '" width="' . (int)$iconWidth . '" height="' . (int)$iconHeight . '"'
-                    . $this->getBorderAttr(' border="0"') . $this->getAltParam($conf) . ' />';
-            }
-            if ($conf['icon_link'] && !$conf['combinedLink']) {
-                $icon = $this->wrap($icon, $theLinkWrap);
-            }
-            $icon = isset($conf['icon.']) ? $this->stdWrap($icon, $conf['icon.']) : $icon;
-        }
-        $size = '';
-        if ($conf['size'] ?? false) {
-            $size = isset($conf['size.']) ? $this->stdWrap($theSize, $conf['size.']) : $theSize;
-        }
-        // Wrapping file label
-        if ($conf['removePrependedNumbers'] ?? false) {
-            $theValue = preg_replace('/_[0-9][0-9](\\.[[:alnum:]]*)$/', '\\1', $theValue);
-        }
-        if (isset($conf['labelStdWrap.'])) {
-            $theValue = $this->stdWrap($theValue, $conf['labelStdWrap.']);
-        }
-        // Wrapping file
-        $wrap = isset($conf['wrap.'])
-            ? $this->stdWrap($conf['wrap'] ?? '', $conf['wrap.'])
-            : ($conf['wrap'] ?? '');
-        if ($conf['combinedLink'] ?? false) {
-            $theValue = $icon . $theValue;
-            if ($conf['ATagBeforeWrap']) {
-                $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
-            } else {
-                $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
-            }
-            $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
-            // output
-            $output = $file . $size;
-        } else {
-            if ($conf['ATagBeforeWrap'] ?? false) {
-                $theValue = $this->wrap($this->wrap($theValue, $wrap), $theLinkWrap);
-            } else {
-                $theValue = $this->wrap($this->wrap($theValue, $theLinkWrap), $wrap);
-            }
-            $file = isset($conf['file.']) ? $this->stdWrap($theValue, $conf['file.']) : $theValue;
-            // output
-            $output = $icon . $file . $size;
-        }
-        if (isset($conf['stdWrap.'])) {
-            $output = $this->stdWrap($output, $conf['stdWrap.']);
-        }
-        return $output;
-    }
-
-    /**
      * Performs basic mathematical evaluation of the input string. Does NOT take parathesis and operator precedence into account! (for that, see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction())
      *
      * @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used.
@@ -3756,26 +3402,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
-     * This explodes a comma-list into an array where the values are parsed through ContentObjectRender::calc() and cast to (int)(so you are sure to have integers in the output array)
-     * Used to split and calculate min and max values for GMENUs.
-     *
-     * @param string $delim Delimited to explode by
-     * @param string $string The string with parts in (where each part is evaluated by ->calc())
-     * @return array And array with evaluated values.
-     * @see calc(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::makeGifs()
-     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10. It is solely used in GMENU, which can be handled there directly.
-     */
-    public function calcIntExplode($delim, $string)
-    {
-        trigger_error('calcIntExplode will be removed in TYPO3 v10.', E_USER_DEPRECATED);
-        $temp = explode($delim, $string);
-        foreach ($temp as $key => $val) {
-            $temp[$key] = (int)$this->calc($val);
-        }
-        return $temp;
-    }
-
-    /**
      * Implements the "split" property of stdWrap; Splits a string based on a token (given in TypoScript properties), sets the "current" value to each part and then renders a content object pointer to by a number.
      * In classic TypoScript (like 'content (default)'/'styles.content (default)') this is used to render tables, splitting rows and cells by tokens and putting them together again wrapped in <td> tags etc.
      * Implements the "optionSplit" processing of the TypoScript options for each splitted value to parse.
@@ -3783,7 +3409,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $value The string value to explode by $conf[token] and process each part
      * @param array $conf TypoScript properties for "split
      * @return string Compiled result
-     * @access private
+     * @internal
      * @see stdWrap(), \TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject::procesItemStates()
      */
     public function splitObj($value, $conf)
@@ -4117,7 +3743,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $theValue The value to process.
      * @param array $conf TypoScript configuration for parseFunc
      * @return string The processed value
-     * @access private
+     * @internal
      * @see parseFunc()
      */
     public function _parseFunc($theValue, $conf)
@@ -4355,7 +3981,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $theValue The input value
      * @param array $conf TypoScript options
      * @return string The processed input value being returned; Splitted lines imploded by LF again.
-     * @access private
+     * @internal
      */
     public function encaps_lineSplit($theValue, $conf)
     {
@@ -4703,8 +4329,8 @@ class ContentObjectRenderer implements LoggerAwareInterface
                     $processedFileObject = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $processingConfiguration);
                     if ($processedFileObject->isProcessed()) {
                         $imageResource = [
-                            0 => $processedFileObject->getProperty('width'),
-                            1 => $processedFileObject->getProperty('height'),
+                            0 => (int)$processedFileObject->getProperty('width'),
+                            1 => (int)$processedFileObject->getProperty('height'),
                             2 => $processedFileObject->getExtension(),
                             3 => $processedFileObject->getPublicUrl(),
                             'origFile' => $fileObject->getPublicUrl(),
@@ -4953,7 +4579,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
                         //   field = backend_layout
                         //   ifEmpty.data = levelfield:-2, backend_layout_next_level, slide
                         //   ifEmpty.ifEmpty = default
-                        $retVal = $GLOBALS['TSFE']->page['backend_layout'];
+                        $retVal = $tsfe->page['backend_layout'];
 
                         // If it is set to "none" - don't use any
                         if ($retVal === '-1') {
@@ -5068,6 +4694,32 @@ class ContentObjectRenderer implements LoggerAwareInterface
                         if (!is_scalar($retVal)) {
                             $retVal = '';
                         }
+                        break;
+                    case 'site':
+                        $request = $GLOBALS['TYPO3_REQUEST'] ?? null;
+                        $site = $request ? $request->getAttribute('site') : null;
+                        if ($site instanceof Site) {
+                            if ($key === 'identifier') {
+                                $retVal = $site->getIdentifier();
+                            } else {
+                                try {
+                                    $retVal = ArrayUtility::getValueByPath($site->getConfiguration(), $key, '.');
+                                } catch (MissingArrayPathException $exception) {
+                                    $this->logger->warning(sprintf('getData() with "%s" failed', $key), ['exception' => $exception]);
+                                }
+                            }
+                        }
+                        break;
+                    case 'sitelanguage':
+                        $request = $GLOBALS['TYPO3_REQUEST'] ?? null;
+                        $siteLanguage = $request ? $request->getAttribute('language') : null;
+                        if ($siteLanguage instanceof SiteLanguage) {
+                            $config = $siteLanguage->toArray();
+                            if (isset($config[$key])) {
+                                $retVal = $config[$key];
+                            }
+                        }
+                        break;
                 }
             }
 
@@ -5154,7 +4806,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param bool $slideBack If set, then we will traverse through the rootline from outer level towards the root level until the value found is TRUE
      * @param mixed $altRootLine If you supply an array for this it will be used as an alternative root line array
      * @return string The value from the field of the rootline.
-     * @access private
+     * @internal
      * @see getData()
      */
     public function rootLineValue($key, $field, $slideBack = false, $altRootLine = '')
@@ -5212,7 +4864,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param int $key The integer to transform
      * @param array $arr array in which the key should be found.
      * @return int The processed integer key value.
-     * @access private
+     * @internal
      * @see getData()
      */
     public function getKey($key, $arr)
@@ -5328,7 +4980,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         $tsfe = $this->getTypoScriptFrontendController();
 
         $linkParameter = trim(
-            (isset($conf['parameter.']) ?? '')
+            (isset($conf['parameter.']))
             ? $this->stdWrap($conf['parameter'] ?? '', $conf['parameter.'])
             : ($conf['parameter'] ?? '')
         );
@@ -5362,7 +5014,8 @@ class ContentObjectRenderer implements LoggerAwareInterface
             /** @var AbstractTypolinkBuilder $linkBuilder */
             $linkBuilder = GeneralUtility::makeInstance(
                 $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
-                $this
+                $this,
+                $tsfe
             );
             try {
                 list($this->lastTypoLinkUrl, $linkText, $target) = $linkBuilder->build($linkDetails, $linkText, $target, $conf);
@@ -5534,7 +5187,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         }
         if (is_array($urlParameters)) {
             if (!empty($urlParameters)) {
-                $conf['additionalParams'] .= GeneralUtility::implodeArrayForUrl('', $urlParameters);
+                $conf['additionalParams'] .= HttpUtility::buildQueryString($urlParameters, '&');
             }
         } else {
             $conf['additionalParams'] .= $urlParameters;
@@ -5583,33 +5236,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
-     * Generates a typolink and returns the two link tags - start and stop - in an array
-     *
-     * @param array $conf "typolink" TypoScript properties
-     * @return array An array with two values in key 0+1, each value being the start and close <a>-tag of the typolink properties being inputted in $conf
-     * @see typolink()
-     */
-    public function typolinkWrap($conf)
-    {
-        $k = md5(microtime());
-        return explode($k, $this->typoLink($k, $conf));
-    }
-
-    /**
-     * Returns the current page URL
-     *
-     * @param array|string $urlParameters As an array key/value pairs represent URL parameters to set. Values NOT URL-encoded yet, keys should be URL-encoded if needed. As a string the parameter is expected to be URL-encoded already.
-     * @param int $id An alternative ID to the current id ($GLOBALS['TSFE']->id)
-     * @return string The URL
-     * @see getTypoLink_URL()
-     */
-    public function currentPageUrl($urlParameters = [], $id = 0)
-    {
-        $tsfe = $this->getTypoScriptFrontendController();
-        return $this->getTypoLink_URL($id ?: $tsfe->id, $urlParameters, $tsfe->sPre);
-    }
-
-    /**
      * Loops over all configured URL modifier hooks (if available) and returns the generated URL or NULL if no URL was generated.
      *
      * @param string $context The context in which the method is called (e.g. typoLink).
@@ -5697,14 +5323,13 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param mixed  $type - either "ascii" or a number between -10 and 10, taken from config.spamProtectEmailAddresses
      * @return string encoded version of $string
      */
-    protected function encryptEmail($string, $type)
+    protected function encryptEmail(string $string, $type): string
     {
         $out = '';
         // obfuscates using the decimal HTML entity references for each character
         if ($type === 'ascii') {
-            $stringLength = strlen($string);
-            for ($a = 0; $a < $stringLength; $a++) {
-                $out .= '&#' . ord(substr($string, $a, 1)) . ';';
+            foreach (preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY) as $char) {
+                $out .= '&#' . mb_ord($char) . ';';
             }
         } else {
             // like str_rot13() but with a variable offset and a wider character range
@@ -5736,14 +5361,13 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param mixed  $type - either "ascii" or a number between -10 and 10 taken from config.spamProtectEmailAddresses
      * @return string decoded version of $string
      */
-    protected function decryptEmail($string, $type)
+    protected function decryptEmail(string $string, $type): string
     {
         $out = '';
         // obfuscates using the decimal HTML entity references for each character
         if ($type === 'ascii') {
-            $stringLength = strlen($string);
-            for ($a = 0; $a < $stringLength; $a++) {
-                $out .= '&#' . ord(substr($string, $a, 1)) . ';';
+            foreach (preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY) as $char) {
+                $out .= '&#' . mb_ord($char) . ';';
             }
         } else {
             // like str_rot13() but with a variable offset and a wider character range
@@ -5831,7 +5455,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
             $newQueryArray = $currentQueryArray;
         }
         ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, $forceOverruleArguments);
-        return GeneralUtility::implodeArrayForUrl('', $newQueryArray, '', false, true);
+        return HttpUtility::buildQueryString($newQueryArray, '&');
     }
 
     /***********************************************
@@ -6056,7 +5680,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      */
     public function sendNotifyEmail($message, $recipients, $cc, $senderAddress, $senderName = '', $replyTo = '')
     {
-        /** @var $mail MailMessage */
+        /** @var MailMessage $mail */
         $mail = GeneralUtility::makeInstance(MailMessage::class);
         $senderName = trim($senderName);
         $senderAddress = trim($senderAddress);
@@ -6085,7 +5709,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
             $parsedCc = MailUtility::parseAddresses($cc);
             if (!empty($parsedCc)) {
                 $from = $mail->getFrom();
-                /** @var $mail MailMessage */
+                /** @var MailMessage $mail */
                 $mail = GeneralUtility::makeInstance(MailMessage::class);
                 if (!empty($parsedReplyTo)) {
                     $mail->setReplyTo($parsedReplyTo);
@@ -6132,28 +5756,6 @@ class ContentObjectRenderer implements LoggerAwareInterface
      ***********************************************/
 
     /**
-     * Returns a part of a WHERE clause which will filter out records with start/end times or hidden/fe_groups fields
-     * set to values that should de-select them according to the current time, preview settings or user login.
-     * Definitely a frontend function.
-     * THIS IS A VERY IMPORTANT FUNCTION: Basically you must add the output from this function for EVERY select query you create
-     * for selecting records of tables in your own applications - thus they will always be filtered according to the "enablefields"
-     * configured in TCA
-     * Simply calls \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() BUT will send the show_hidden flag along!
-     * This means this function will work in conjunction with the preview facilities of the frontend engine/Admin Panel.
-     *
-     * @param string $table The table for which to get the where clause
-     * @param bool $show_hidden If set, then you want NOT to filter out hidden records. Otherwise hidden record are filtered based on the current preview settings.
-     * @param array $ignore_array Array you can pass where keys can be "disabled", "starttime", "endtime", "fe_group" (keys from "enablefields" in TCA) and if set they will make sure that part of the clause is not added. Thus disables the specific part of the clause. For previewing etc.
-     * @return string The part of the where clause on the form " AND [fieldname]=0 AND ...". Eg. " AND hidden=0 AND starttime < 123345567
-     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0.
-     */
-    public function enableFields($table, $show_hidden = false, array $ignore_array = [])
-    {
-        trigger_error('cObj->enableFields() will be removed in TYPO3 v10. should be used from the PageRepository->enableFields() functionality directly.', E_USER_DEPRECATED);
-        return $this->getTypoScriptFrontendController()->sys_page->enableFields($table, $show_hidden ? true : -1, $ignore_array);
-    }
-
-    /**
      * Generates a list of Page-uid's from $id. List does not include $id itself
      * (unless the id specified is negative in which case it does!)
      * The only pages WHICH PREVENTS DECENDING in a branch are
@@ -6271,7 +5873,9 @@ class ContentObjectRenderer implements LoggerAwareInterface
                     $queryBuilder->expr()->eq(
                         'pid',
                         $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
-                    )
+                    ),
+                    // tree is only built by language=0 pages
+                    $queryBuilder->expr()->eq('sys_language_uid', 0)
                 )
                 ->orderBy('sorting');
 
@@ -6379,15 +5983,21 @@ class ContentObjectRenderer implements LoggerAwareInterface
                     $theList[] = $addId;
                 }
             }
-            GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist')->insert(
-                'cache_treelist',
-                [
-                    'md5hash' => $requestHash,
-                    'pid' => $id,
-                    'treelist' => implode(',', $theList),
-                    'tstamp' => $GLOBALS['EXEC_TIME']
-                ]
-            );
+
+            $cacheEntry = [
+                'md5hash' => $requestHash,
+                'pid' => $id,
+                'treelist' => implode(',', $theList),
+                'tstamp' => $GLOBALS['EXEC_TIME'],
+            ];
+
+            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('cache_treelist');
+            try {
+                $connection->transactional(function ($connection) use ($cacheEntry) {
+                    $connection->insert('cache_treelist', $cacheEntry);
+                });
+            } catch (\Throwable $e) {
+            }
         }
 
         return implode(',', $theList);
@@ -6498,7 +6108,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @return mixed A SELECT query if $returnQueryArray is FALSE, otherwise the SELECT query in an array as parts.
      * @throws \RuntimeException
      * @throws \InvalidArgumentException
-     * @access private
+     * @internal
      * @see CONTENT(), numRows()
      */
     public function getQuery($table, $conf, $returnQueryArray = false)
@@ -6726,9 +6336,9 @@ class ContentObjectRenderer implements LoggerAwareInterface
             $knownAliases[$tableReference] = true;
 
             $fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
-                    $tableReference,
-                    $queryBuilder->getQueryPart('join'),
-                    $knownAliases
+                $tableReference,
+                $queryBuilder->getQueryPart('join'),
+                $knownAliases
                 );
         }
 
@@ -6866,42 +6476,11 @@ class ContentObjectRenderer implements LoggerAwareInterface
             $constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
         }
 
-        // Check if the table is translatable, and set the language field by default from the TCA information
-        $languageField = '';
-        if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
-            if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
-                $languageField = $conf['languageField'];
-            } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
-                $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
-            }
-        }
-
-        if (!empty($languageField)) {
-            // The sys_language record UID of the content of the page
-            /** @var LanguageAspect $languageAspect */
-            $languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
-
-            if ($languageAspect->doOverlays() && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
-                // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
-                // OVERLAY the records with localized versions!
-                $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
-                // 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'];
-                if (trim($includeRecordsWithoutDefaultTranslation) !== '') {
-                    $languageQuery = $expressionBuilder->orX(
-                        $languageQuery,
-                        $expressionBuilder->andX(
-                            $expressionBuilder->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0),
-                            $expressionBuilder->eq($languageField, $languageAspect->getContentId())
-                        )
-                    );
-                }
-            } else {
-                $languageQuery = $expressionBuilder->eq($languageField, $languageAspect->getContentId());
-            }
-            $constraints[] = $languageQuery;
+        // Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched
+        // but only do this for TCA tables that have languages enabled
+        $languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class));
+        if ($languageConstraint !== null) {
+            $constraints[] = $languageConstraint;
         }
 
         // Enablefields
@@ -6938,6 +6517,81 @@ class ContentObjectRenderer implements LoggerAwareInterface
     }
 
     /**
+     * Adds parts to the WHERE clause that are related to language.
+     * This only works on TCA tables which have the [ctrl][languageField] field set or if they
+     * have select.languageField = my_language_field set explicitly.
+     *
+     * It is also possible to disable the language restriction for a query by using select.languageField = 0,
+     * if select.languageField is not explicitly set, the TCA default values are taken.
+     *
+     * If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted:
+     *
+     * If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are
+     * fetched (the overlays are taken care of later-on).
+     * if the current language has overlays but also records without localization-parent (free mode) available,
+     * then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1
+     * which overrules the overlayType within the language aspect.
+     *
+     * If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records
+     * for the current language.
+     *
+     * @param ExpressionBuilder $expressionBuilder
+     * @param string $table
+     * @param array $conf
+     * @param Context $context
+     * @return string|\TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression|null
+     * @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
+     */
+    protected function getLanguageRestriction(ExpressionBuilder $expressionBuilder, string $table, array $conf, Context $context)
+    {
+        $languageField = '';
+        $localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null;
+        // Check if the table is translatable, and set the language field by default from the TCA information
+        if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
+            if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
+                $languageField = $conf['languageField'];
+            } elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) {
+                $languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
+            }
+        }
+
+        // No language restriction enabled explicitly or available via TCA
+        if (empty($languageField)) {
+            return null;
+        }
+
+        /** @var LanguageAspect $languageAspect */
+        $languageAspect = $context->getAspect('language');
+        if ($languageAspect->doOverlays() && !empty($localizationParentField)) {
+            // Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
+            // OVERLAY the records with localized versions!
+            $languageQuery = $expressionBuilder->in($languageField, [0, -1]);
+            // Use this option to include records that don't have a default translation ("free mode")
+            // (originalpointerfield is 0 and the language field contains the requested language)
+            if (isset($conf['includeRecordsWithoutDefaultTranslation']) || $conf['includeRecordsWithoutDefaultTranslation.']) {
+                $includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.']) ?
+                    $this->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.']) : $conf['includeRecordsWithoutDefaultTranslation'];
+                $includeRecordsWithoutDefaultTranslation = trim($includeRecordsWithoutDefaultTranslation) !== '';
+            } else {
+                // Option was not explicitly set, check what's in for the language overlay type.
+                $includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING;
+            }
+            if ($includeRecordsWithoutDefaultTranslation) {
+                $languageQuery = $expressionBuilder->orX(
+                    $languageQuery,
+                    $expressionBuilder->andX(
+                        $expressionBuilder->eq($localizationParentField, 0),
+                        $expressionBuilder->eq($languageField, $languageAspect->getContentId())
+                    )
+                );
+            }
+            return $languageQuery;
+        }
+        // No overlays = only fetch records given for the requested language and "all languages"
+        return $expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]);
+    }
+
+    /**
      * Helper function for getQuery, sanitizing the select part
      *
      * This functions checks if the necessary fields are part of the select
@@ -6946,7 +6600,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $selectPart Select part
      * @param string $table Table to select from
      * @return string Sanitized select part
-     * @access private
+     * @internal
      * @see getQuery
      */
     protected function sanitizeSelectPart($selectPart, $table)
@@ -6982,7 +6636,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      *
      * @param array $listArr Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed.
      * @return array Returns the array of remaining page UID numbers
-     * @access private
+     * @internal
      * @see checkPid()
      */
     public function checkPidArray($listArr)
@@ -7025,7 +6679,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      *
      * @param int $uid Page UID to test
      * @return bool TRUE if OK
-     * @access private
+     * @internal
      * @see checkPidArray()
      */
     public function checkPid($uid)
@@ -7064,7 +6718,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $table Table to select records from
      * @param array $conf Select part of CONTENT definition
      * @return array List of values to replace markers with
-     * @access private
+     * @internal
      * @see getQuery()
      */
     public function getQueryMarkers($table, $conf)
@@ -7146,20 +6800,57 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $content A content string containing the content related to the edit panel. For cObject "EDITPANEL" this is empty but not so for the stdWrap property. The edit panel is appended to this string and returned.
      * @param array $conf TypoScript configuration properties for the editPanel
      * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW
-     * @param array $dataArr Alternative data array to use. Default is $this->data
+     * @param array $dataArray Alternative data array to use. Default is $this->data
      * @return string The input content string with the editPanel appended. This function returns only an edit panel appended to the content string if a backend user is logged in (and has the correct permissions). Otherwise the content string is directly returned.
      */
-    public function editPanel($content, $conf, $currentRecord = '', $dataArr = [])
+    public function editPanel($content, $conf, $currentRecord = '', $dataArray = [])
     {
-        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
-            if (!$currentRecord) {
-                $currentRecord = $this->currentRecord;
+        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
+            return $content;
+        }
+        if (!$this->getTypoScriptFrontendController()->displayEditIcons) {
+            return $content;
+        }
+
+        if (!$currentRecord) {
+            $currentRecord = $this->currentRecord;
+        }
+        if (empty($dataArray)) {
+            $dataArray = $this->data;
+        }
+
+        if ($conf['newRecordFromTable']) {
+            $currentRecord = $conf['newRecordFromTable'] . ':NEW';
+            $conf['allow'] = 'new';
+            $checkEditAccessInternals = false;
+        } else {
+            $checkEditAccessInternals = true;
+        }
+        list($table, $uid) = explode(':', $currentRecord);
+        // Page ID for new records, 0 if not specified
+        $newRecordPid = (int)$conf['newRecordInPid'];
+        $newUid = null;
+        if (!$conf['onlyCurrentPid'] || $dataArray['pid'] == $this->getTypoScriptFrontendController()->id) {
+            if ($table === 'pages') {
+                $newUid = $uid;
+            } else {
+                if ($conf['newRecordFromTable']) {
+                    $newUid = $this->getTypoScriptFrontendController()->id;
+                    if ($newRecordPid) {
+                        $newUid = $newRecordPid;
+                    }
+                } else {
+                    $newUid = -1 * $uid;
+                }
             }
-            if (empty($dataArr)) {
-                $dataArr = $this->data;
+        }
+        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, $checkEditAccessInternals) && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
+            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
+            if ($editClass) {
+                $edit = GeneralUtility::makeInstance($editClass);
+                $allowedActions = $this->getFrontendBackendUser()->getAllowedEditActions($table, $conf, $dataArray['pid']);
+                $content = $edit->editPanel($content, $conf, $currentRecord, $dataArray, $table, $allowedActions, $newUid, []);
             }
-            // Delegate rendering of the edit panel to the frontend edit
-            $content = $this->getFrontendBackendUser()->frontendEdit->displayEditPanel($content, $conf, $currentRecord, $dataArr);
         }
         return $content;
     }
@@ -7172,21 +6863,46 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $params The parameters defining which table and fields to edit. Syntax is [tablename]:[fieldname],[fieldname],[fieldname],... OR [fieldname],[fieldname],[fieldname],... (basically "[tablename]:" is optional, default table is the one of the "current record" used in the function). The fieldlist is sent as "&columnsOnly=" parameter to FormEngine
      * @param array $conf TypoScript properties for configuring the edit icons.
      * @param string $currentRecord The "table:uid" of the record being shown. If empty string then $this->currentRecord is used. For new records (set by $conf['newRecordFromTable']) it's auto-generated to "[tablename]:NEW
-     * @param array $dataArr Alternative data array to use. Default is $this->data
+     * @param array $dataArray Alternative data array to use. Default is $this->data
      * @param string $addUrlParamStr Additional URL parameters for the link pointing to FormEngine
      * @return string The input content string, possibly with edit icons added (not necessarily in the end but just after the last string of normal content.
      */
-    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArr = [], $addUrlParamStr = '')
+    public function editIcons($content, $params, array $conf = [], $currentRecord = '', $dataArray = [], $addUrlParamStr = '')
     {
-        if ($this->getTypoScriptFrontendController()->isBackendUserLoggedIn() && $this->getFrontendBackendUser()->frontendEdit instanceof FrontendEditingController) {
-            if (!$currentRecord) {
-                $currentRecord = $this->currentRecord;
-            }
-            if (empty($dataArr)) {
-                $dataArr = $this->data;
+        if (!$this->getTypoScriptFrontendController()->isBackendUserLoggedIn()) {
+            return $content;
+        }
+        if (!$this->getTypoScriptFrontendController()->displayFieldEditIcons) {
+            return $content;
+        }
+        if (!$currentRecord) {
+            $currentRecord = $this->currentRecord;
+        }
+        if (empty($dataArray)) {
+            $dataArray = $this->data;
+        }
+        // Check incoming params:
+        list($currentRecordTable, $currentRecordUID) = explode(':', $currentRecord);
+        list($fieldList, $table) = array_reverse(GeneralUtility::trimExplode(':', $params, true));
+        // Reverse the array because table is optional
+        if (!$table) {
+            $table = $currentRecordTable;
+        } elseif ($table != $currentRecordTable) {
+            // If the table is set as the first parameter, and does not match the table of the current record, then just return.
+            return $content;
+        }
+
+        $editUid = $dataArray['_LOCALIZED_UID'] ?: $currentRecordUID;
+        // Edit icons imply that the editing action is generally allowed, assuming page and content element permissions permit it.
+        if (!array_key_exists('allow', $conf)) {
+            $conf['allow'] = 'edit';
+        }
+        if ($table && $this->getFrontendBackendUser()->allowedToEdit($table, $dataArray, $conf, true) && $fieldList && $this->getFrontendBackendUser()->allowedToEditLanguage($table, $dataArray)) {
+            $editClass = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/classes/class.frontendedit.php']['edit'];
+            if ($editClass) {
+                $edit = GeneralUtility::makeInstance($editClass);
+                $content = $edit->editIcons($content, $params, $conf, $currentRecord, $dataArray, $addUrlParamStr, $table, $editUid, $fieldList);
             }
-            // Delegate rendering of the edit panel to frontend edit class.
-            $content = $this->getFrontendBackendUser()->frontendEdit->displayEditIcons($content, $params, $conf, $currentRecord, $dataArr, $addUrlParamStr);
         }
         return $content;
     }
@@ -7197,7 +6913,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      * @param string $table The table name
      * @param array $row The data record
      * @return bool
-     * @access private
+     * @internal
      * @see editPanelPreviewBorder()
      */
     public function isDisabled($table, $row)
@@ -7245,7 +6961,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
 
         $cacheKey = $this->calculateCacheKey($configuration);
         if (!empty($cacheKey)) {
-            /** @var $cacheFrontend \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
+            /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cacheFrontend */
             $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)
                 ->getCache('cache_hash');
             $content = $cacheFrontend->get($cacheKey);