From c53ecec4f0fcee73b0adbd84cc2298f4b6b5409f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolaj=20Wojtkowiak-Pfa=CC=88nder?= Date: Thu, 17 Nov 2016 12:03:49 +0100 Subject: [PATCH] [BUGFIX] #17354 fallback for menus. "content_fallback;1,0" has no affect for page records https://forge.typo3.org/issues/17354 The configuration setting: config.sys_language_mode = content_fallback; 2,1 must be changed into: config.sys_language_mode = content_fallback config.languageFallbackChain = 2,1 --- .../Persistence/Generic/Storage/Typo3DbBackend.php | 4 +- .../Classes/Aspect/FileMetadataOverlayAspect.php | 10 +- .../Category/Collection/CategoryCollection.php | 10 +- .../Classes/ContentObject/FilesContentObject.php | 7 +- .../Menu/AbstractMenuContentObject.php | 4 +- .../Classes/ContentObject/RecordsContentObject.php | 52 ++-- .../Controller/TypoScriptFrontendController.php | 75 ++++- .../frontend/Classes/Page/PageRepository.php | 309 +++++++++++++-------- .../Menu/AbstractMenuContentObjectTest.php | 4 +- 9 files changed, 308 insertions(+), 167 deletions(-) diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php index 7f4dcd8..a7be4a1 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php @@ -838,7 +838,9 @@ class Typo3DbBackend implements BackendInterface, \TYPO3\CMS\Core\SingletonInter ) { if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], [-1, 0])) { $overlayMode = $querySettings->getLanguageMode() === 'strict' ? 'hideNonTranslated' : ''; - $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode); + $row = $pageRepository->getRecordOverlayWithFallback( + $tableName, $row, $querySettings->getLanguageUid(), $overlayMode + ); } } if ($row !== null && is_array($row)) { diff --git a/typo3/sysext/frontend/Classes/Aspect/FileMetadataOverlayAspect.php b/typo3/sysext/frontend/Classes/Aspect/FileMetadataOverlayAspect.php index 9e1dd3c..abdfd13 100644 --- a/typo3/sysext/frontend/Classes/Aspect/FileMetadataOverlayAspect.php +++ b/typo3/sysext/frontend/Classes/Aspect/FileMetadataOverlayAspect.php @@ -36,12 +36,10 @@ class FileMetadataOverlayAspect { $overlaidMetaData = $data->getArrayCopy(); $this->getTsfe()->sys_page->versionOL('sys_file_metadata', $overlaidMetaData); - $overlaidMetaData = $this->getTsfe()->sys_page->getRecordOverlay( - 'sys_file_metadata', - $overlaidMetaData, - $this->getTsfe()->sys_language_content, - $this->getTsfe()->sys_language_contentOL - ); + $overlaidMetaData = $this->getTsfe()->sys_page->getRecordOverlayWithFallback( + 'sys_file_metadata', + $overlaidMetaData + ); if ($overlaidMetaData !== null) { $data->exchangeArray($overlaidMetaData); } diff --git a/typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php b/typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php index 4b976fe..21edc9d 100644 --- a/typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php +++ b/typo3/sysext/frontend/Classes/Category/Collection/CategoryCollection.php @@ -138,12 +138,10 @@ class CategoryCollection extends \TYPO3\CMS\Core\Category\Collection\CategoryCol if ($this->getItemTableName() === 'pages') { $record = $tsfe->sys_page->getPageOverlay($record); } else { - $record = $tsfe->sys_page->getRecordOverlay( - $this->getItemTableName(), - $record, - $tsfe->sys_language_content, - $tsfe->sys_language_contentOL - ); + $record = $this->getFrontendObject()->sys_page->getRecordOverlayWithFallback( + $this->getItemTableName(), + $record + ); } } // Record may have been unset during the overlay process diff --git a/typo3/sysext/frontend/Classes/ContentObject/FilesContentObject.php b/typo3/sysext/frontend/Classes/ContentObject/FilesContentObject.php index c8dcdc0..ba10d3e 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/FilesContentObject.php +++ b/typo3/sysext/frontend/Classes/ContentObject/FilesContentObject.php @@ -189,12 +189,7 @@ class FilesContentObject extends AbstractContentObject if ($referencesForeignTable === 'pages') { $element = $pageRepository->getPageOverlay($element); } else { - $element = $pageRepository->getRecordOverlay( - $referencesForeignTable, - $element, - $GLOBALS['TSFE']->sys_language_content, - $GLOBALS['TSFE']->sys_language_contentOL - ); + $element = $pageRepository->getRecordOverlayWithFallback($referencesForeignTable, $element); } } diff --git a/typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php b/typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php index 8935a0a..78c7b5a 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php +++ b/typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php @@ -1246,7 +1246,7 @@ abstract class AbstractMenuContentObject $tok = true; // There is an alternative language active AND the current page requires a translation: if ($tsfe->sys_language_uid && GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) { - if (!$data['_PAGES_OVERLAY']) { + if (!$data['_PAGES_OVERLAY'] || (int)$data['sys_language_uid'] !== (int)$GLOBALS['TSFE']->sys_language_uid) { $tok = false; } } @@ -2160,7 +2160,7 @@ abstract class AbstractMenuContentObject while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($resource)) { $this->sys_page->versionOL('tt_content', $row); if ($tsfe->sys_language_contentOL && $basePageRow['_PAGES_OVERLAY_LANGUAGE']) { - $row = $this->sys_page->getRecordOverlay('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE'], $tsfe->sys_language_contentOL); + $row = $this->sys_page->getRecordOverlayWithFallback('tt_content', $row, $basePageRow['_PAGES_OVERLAY_LANGUAGE']); } if ($this->mconf['sectionIndex.']['type'] !== 'all') { $doIncludeInSectionIndex = $row['sectionIndex'] >= 1; diff --git a/typo3/sysext/frontend/Classes/ContentObject/RecordsContentObject.php b/typo3/sysext/frontend/Classes/ContentObject/RecordsContentObject.php index a3d79d3..b3e7787 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/RecordsContentObject.php +++ b/typo3/sysext/frontend/Classes/ContentObject/RecordsContentObject.php @@ -49,12 +49,14 @@ class RecordsContentObject extends AbstractContentObject $this->itemArray = []; $this->data = []; + $tsfe = $this->getTypoScriptFrontendController(); + $theValue = ''; - $originalRec = $GLOBALS['TSFE']->currentRecord; + $originalRec = $tsfe->currentRecord; // If the currentRecord is set, we register, that this record has invoked this function. // It's should not be allowed to do this again then!! if ($originalRec) { - ++$GLOBALS['TSFE']->recordRegister[$originalRec]; + ++$tsfe->recordRegister[$originalRec]; } $tables = isset($conf['tables.']) ? $this->cObj->stdWrap($conf['tables'], $conf['tables.']) : $conf['tables']; @@ -89,31 +91,40 @@ class RecordsContentObject extends AbstractContentObject foreach ($this->itemArray as $val) { $row = $this->data[$val['table']][$val['id']]; // Perform overlays if necessary (records coming from category collections are already overlaid) - if ($source) { - // Versioning preview - $GLOBALS['TSFE']->sys_page->versionOL($val['table'], $row); - // Language overlay - if (is_array($row) && $GLOBALS['TSFE']->sys_language_contentOL) { - if ($val['table'] === 'pages') { - $row = $GLOBALS['TSFE']->sys_page->getPageOverlay($row); - } else { - $row = $GLOBALS['TSFE']->sys_page->getRecordOverlay($val['table'], $row, $GLOBALS['TSFE']->sys_language_content, $GLOBALS['TSFE']->sys_language_contentOL); - } - } - } + if ($source) { + // Versioning preview + $tsfe->sys_page->versionOL($val['table'], $row); + // Language overlay + if (is_array($row) && $tsfe->sys_language_contentOL) { + if ($val['table'] === 'pages') { + $row = $tsfe->sys_page->getPageOverlay($row); + } else { + $fallbackChain = NULL; + if (isset($conf['languageFallbackChain'])) { + $fallbackChain = GeneralUtility::intExplode(',', $conf['languageFallbackChain']); + } + + $pageLanguageBinding = !isset($conf['respectPageLanguageBinding']) || (int)$conf['respectPageLanguageBinding'] !== '0'; + + $row = $tsfe->sys_page->getRecordOverlayWithFallback( + $val['table'], $row, NULL, NULL, $fallbackChain, $pageLanguageBinding + ); + } + } + } // Might be unset during the overlay process if (is_array($row)) { $dontCheckPid = isset($conf['dontCheckPid.']) ? $this->cObj->stdWrap($conf['dontCheckPid'], $conf['dontCheckPid.']) : $conf['dontCheckPid']; if (!$dontCheckPid) { $row = $this->cObj->checkPid($row['pid']) ? $row : ''; } - if ($row && !$GLOBALS['TSFE']->recordRegister[$val['table'] . ':' . $val['id']]) { + if ($row && !$tsfe->recordRegister[($val['table'] . ':' . $val['id'])]) { $renderObjName = $conf['conf.'][$val['table']] ?: '<' . $val['table']; $renderObjKey = $conf['conf.'][$val['table']] ? 'conf.' . $val['table'] : ''; $renderObjConf = $conf['conf.'][$val['table'] . '.']; $this->cObj->currentRecordNumber++; $cObj->parentRecordNumber = $this->cObj->currentRecordNumber; - $GLOBALS['TSFE']->currentRecord = $val['table'] . ':' . $val['id']; + $tsfe->currentRecord = $val['table'] . ':' . $val['id']; $this->cObj->lastChanged($row['tstamp']); $cObj->start($row, $val['table']); $tmpValue = $cObj->cObjGetSingle($renderObjName, $renderObjConf, $renderObjKey); @@ -131,7 +142,7 @@ class RecordsContentObject extends AbstractContentObject $theValue = $this->cObj->stdWrap($theValue, $conf['stdWrap.']); } // Restore - $GLOBALS['TSFE']->currentRecord = $originalRec; + $tsfe->currentRecord = $originalRec; if ($originalRec) { --$GLOBALS['TSFE']->recordRegister[$originalRec]; } @@ -233,4 +244,11 @@ class RecordsContentObject extends AbstractContentObject { return $GLOBALS['TT']; } + + /** + * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + */ + protected function getTypoScriptFrontendController() { + return $GLOBALS['TSFE']; + } } diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index 52d00be..d8b0814 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -604,6 +604,20 @@ class TypoScriptFrontendController */ public $sys_language_content = 0; + /** + * Contains the prioritized language fallback chain + * + * @var array + */ + public $languageFallbackChain = array(); + + /** + * Contains the prioritized language fallback chain for the current page + * + * @var array + */ + public $languageFallbackChainWithPageLanguageBinding = array(); + /** * Site content overlay flag; If set - and sys_language_content is > 0 - , * records selected will try to look for a translation pointing to their uid. (If @@ -2694,7 +2708,8 @@ class TypoScriptFrontendController // Get values from TypoScript: $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']); list($this->sys_language_mode, $sys_language_content) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']); - $this->sys_language_contentOL = $this->config['config']['sys_language_overlay']; + $languageFallbackChain = GeneralUtility::intExplode(',', $this->config['config']['languageFallbackChain']); + $this->sys_language_contentOL = $this->config['config']['sys_language_overlay']; // If sys_language_uid is set to another language than default: if ($this->sys_language_uid > 0) { // check whether a shortcut is overwritten by a translated page @@ -2715,14 +2730,30 @@ class TypoScriptFrontendController $this->pageNotFoundAndExit('Page is not available in the requested language (strict).'); break; case 'content_fallback': - $fallBackOrder = GeneralUtility::intExplode(',', $sys_language_content); - foreach ($fallBackOrder as $orderValue) { - if ((string)$orderValue === '0' || !empty($this->sys_page->getPageOverlay($this->id, $orderValue))) { - $this->sys_language_content = $orderValue; - // Setting content uid (but leaving the sys_language_uid) - break; - } - } + // default is to fallback to default language + $this->sys_language_content = 0; + + foreach ($languageFallbackChain as $languageId) { + // break the language chain if the current language is selected + if ($languageId === 0) { + break; + } + + // ignore current and invalid language ids + if ($this->sys_language_uid === $languageId || $languageId < 0) { + continue; + } + + $pageOverlay = $this->sys_page->getPageOverlay($this->id, $languageId); + if (!empty($pageOverlay)) { + // Setting content uid (but leaving the sys_language_uid) + $this->sys_language_content = $languageId; + // overlay page with existing page overlay + $this->page = array_merge($this->page, $pageOverlay); + + break; + } + } break; case 'ignore': $this->sys_language_content = $this->sys_language_uid; @@ -2730,19 +2761,35 @@ class TypoScriptFrontendController default: // Default is that everything defaults to the default language... $this->sys_language_uid = ($this->sys_language_content = 0); + break; } } } } else { - // Setting sys_language if an overlay record was found (which it is only if a language is used) - $this->page = $this->sys_page->getPageOverlay($this->page, $this->sys_language_uid); + // overlay page with existing page overlay + $this->page = array_merge($this->page, $olRec); } + + // calculate the language fallback chains + if ($this->sys_language_mode === 'content_fallback') { + foreach ($languageFallbackChain as $languageId) { + // ignore current and invalid language ids + if ($this->sys_language_uid === $languageId || $languageId < 0) { + continue; + } + + $this->languageFallbackChain[] = $languageId; + if ($languageId === 0 || count($this->sys_page->getPageOverlay($this->id, $languageId))) { + $this->languageFallbackChainWithPageLanguageBinding[] = $languageId; + } + } + } } // Setting sys_language_uid inside sys-page: $this->sys_page->sys_language_uid = $this->sys_language_uid; - // If default translation is not available: - if ((!$this->sys_language_uid || !$this->sys_language_content) && GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'])) { - $message = 'Page is not available in default language.'; + // If default translation is not to be selected + if ($this->page['l18n_cfg']&1 && !($this->sys_language_uid && $this->sys_language_content)) { + $message = 'Page is not available in default language.'; GeneralUtility::sysLog($message, 'cms', GeneralUtility::SYSLOG_SEVERITY_ERROR); $this->pageNotFoundAndExit($message); } diff --git a/typo3/sysext/frontend/Classes/Page/PageRepository.php b/typo3/sysext/frontend/Classes/Page/PageRepository.php index b046296..c33fcb3 100644 --- a/typo3/sysext/frontend/Classes/Page/PageRepository.php +++ b/typo3/sysext/frontend/Classes/Page/PageRepository.php @@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\RootlineUtility; use TYPO3\CMS\Core\Versioning\VersionState; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; /** * Page functions, a lot of sql/pages-related functions @@ -355,9 +356,12 @@ class PageRepository return []; } // Initialize: - if ($lUid < 0) { - $lUid = $this->sys_language_uid; - } + $noSpecialLanguageRequested = FALSE; + if ($lUid < 0) { + $noSpecialLanguageRequested = TRUE; + $lUid = $this->sys_language_uid; + } + $row = null; if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay'])) { foreach ($pagesInput as &$origPage) { @@ -371,6 +375,7 @@ class PageRepository } unset($origPage); } + // If language UID is different from zero, do overlay: if ($lUid) { $fieldArr = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'], true); @@ -390,40 +395,56 @@ class PageRepository $page_ids[] = $origPage; } } - if (!empty($fieldArr)) { - if (!in_array('pid', $fieldArr, true)) { - $fieldArr[] = 'pid'; - } - // NOTE to enabledFields('pages_language_overlay'): - // Currently the showHiddenRecords of TSFE set will allow - // pages_language_overlay records to be selected as they are - // child-records of a page. - // However you may argue that the showHiddenField flag should - // determine this. But that's not how it's done right now. - // Selecting overlay record: - $db = $this->getDatabaseConnection(); - $res = $db->exec_SELECTquery( - implode(',', $fieldArr), - 'pages_language_overlay', - 'pid IN(' . implode(',', $db->cleanIntArray($page_ids)) . ')' - . ' AND sys_language_uid=' . (int)$lUid . $this->enableFields('pages_language_overlay') - ); - $overlays = []; - while ($row = $db->sql_fetch_assoc($res)) { - $this->versionOL('pages_language_overlay', $row); - if (is_array($row)) { - $row['_PAGES_OVERLAY'] = true; - $row['_PAGES_OVERLAY_UID'] = $row['uid']; - $row['_PAGES_OVERLAY_LANGUAGE'] = $lUid; - $origUid = $row['pid']; - // Unset vital fields that are NOT allowed to be overlaid: - unset($row['uid']); - unset($row['pid']); - $overlays[$origUid] = $row; - } - } - $db->sql_free_result($res); - } + + if (!empty($fieldArr)) { + if (!in_array('pid', $fieldArr, TRUE)) { + $fieldArr[] = 'pid'; + } + $languageClause = 'sys_language_uid=' . (int)$lUid . ' '; + + $languageList = array(); + if ($noSpecialLanguageRequested) { + $languageList = array_unique(array_merge( + [ $lUid ], + $this->getTypoScriptFrontendController()->languageFallbackChain, + [ $this->getTypoScriptFrontendController()->sys_language_content ] + )); + + $languageIds = implode(',', array_map('intval', $languageList)); + $languageClause = 'sys_language_uid IN (' . $languageIds . ') '; + } + // NOTE to enabledFields('pages_language_overlay'): + // Currently the showHiddenRecords of TSFE set will allow pages_language_overlay records to be selected as they are child-records of a page. + // However you may argue that the showHiddenField flag should determine this. But that's not how it's done right now. + // Selecting overlay record: + if (!in_array('sys_language_uid', $fieldArr)) { + $fieldArr[] = 'sys_language_uid'; + } + $db = $this->getDatabaseConnection(); + $res = $db->exec_SELECTquery( + implode(',', $fieldArr), + 'pages_language_overlay', + 'pid IN(' . implode(',', $db->cleanIntArray($page_ids)) . ')' + . ' AND ' . $languageClause . $this->enableFields('pages_language_overlay') + ); + + $overlays = array(); + while ($row = $db->sql_fetch_assoc($res)) { + $this->versionOL('pages_language_overlay', $row); + + if (is_array($row)) { + $row['_PAGES_OVERLAY'] = TRUE; + $row['_PAGES_OVERLAY_UID'] = $row['uid']; + $row['_PAGES_OVERLAY_LANGUAGE'] = $lUid; + $origUid = $row['pid']; + // Unset vital fields that are NOT allowed to be overlaid: + unset($row['uid']); + unset($row['pid']); + $overlays[$origUid] = $row; + } + } + $db->sql_free_result($res); + } } // Create output: $pagesOutput = []; @@ -446,9 +467,55 @@ class PageRepository } } } + return $pagesOutput; } + /** + * Creates a language overlay for records stored inside tables which contain the + * translation information themselves. In addition to getRecordOverlay this method + * also checks for specified content language fallbacks and includes them. + * + * @param string $table Table name + * @param array $row Record to overlay. Must containt uid, pid and $table]['ctrl']['languageField'] + * @param int $sys_language_content Pointer to the sys_language uid for content on the site. + * @param string $OLmode Overlay mode. If "hideNonTranslated" then records without translation will not be returned un-translated but unset (and return value is FALSE) + * @param array|NULL $fallbackList ordered fallback list of language ids + * @param bool $pageLanguageBinding use page language binding for the language fallback chain (only used if fallbackList is NULL) + * @return mixed Returns the input record, possibly overlaid with a translation. But if $OLmode is "hideNonTranslated" then it will return NULL if no translation is found. + * @throws \UnexpectedValueException + */ + public function getRecordOverlayWithFallback($table, $row, $sys_language_content = NULL, $OLmode = NULL, $fallbackList = NULL, $pageLanguageBinding = TRUE) { + $tsfe = $this->getTypoScriptFrontendController(); + $sys_language_content = $sys_language_content !== NULL ? $sys_language_content : $tsfe->sys_language_content; + $OLmode = $OLmode !== NULL ? $OLmode: $tsfe->sys_language_contentOL; + if ($fallbackList === NULL) { + $fallbackList = $tsfe->languageFallbackChainWithPageLanguageBinding; + if (!$pageLanguageBinding) { + $fallbackList = $tsfe->languageFallbackChain; + } + } + + $record = $this->getRecordOverlayWithoutFallback($table, $row, $sys_language_content, $OLmode); + if (!is_array($record) || !isset($record['_LOCALIZED_UID'])) { + if ($sys_language_content && $OLmode !== 'hideNonTranslated' && !empty($fallbackList)) { + foreach ($fallbackList as $fallbackId) { + $fallbackId = (int)$fallbackId; + if ($fallbackId === 0) { + $record = $row; + break; + } + + $record = $this->getRecordOverlayWithoutFallback($table, $row, $fallbackId, $OLmode); + if (isset($record['_LOCALIZED_UID']) && is_array($record)) { + break; + } + } + } + } + return $record; + } + /** * Creates language-overlay for records in general (where translation is found * in records from the same table) @@ -460,82 +527,98 @@ class PageRepository * @throws \UnexpectedValueException * @return mixed Returns the input record, possibly overlaid with a translation. But if $OLmode is "hideNonTranslated" then it will return FALSE if no translation is found. */ - public function getRecordOverlay($table, $row, $sys_language_content, $OLmode = '') - { - if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'])) { - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] as $classRef) { - $hookObject = GeneralUtility::getUserObj($classRef); - if (!$hookObject instanceof PageRepositoryGetRecordOverlayHookInterface) { - throw new \UnexpectedValueException($classRef . ' must implement interface ' . PageRepositoryGetRecordOverlayHookInterface::class, 1269881658); - } - $hookObject->getRecordOverlay_preProcess($table, $row, $sys_language_content, $OLmode, $this); - } - } - if ($row['uid'] > 0 && ($row['pid'] > 0 || in_array($table, $this->tableNamesAllowedOnRootLevel, true))) { - if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) { - // Return record for ALL languages untouched - // TODO: Fix call stack to prevent this situation in the first place - if (!$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'] && (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] !== -1) { - // Will not be able to work with other tables (Just didn't implement it yet; - // Requires a scan over all tables [ctrl] part for first FIND the table that - // carries localization information for this table (which could even be more - // than a single table) and then use that. Could be implemented, but obviously - // takes a little more....) Will try to overlay a record only if the - // sys_language_content value is larger than zero. - if ($sys_language_content > 0) { - // Must be default language, otherwise no overlaying - if ((int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) { - // Select overlay record: - $res = $this->getDatabaseConnection()->exec_SELECTquery('*', $table, 'pid=' . (int)$row['pid'] . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$sys_language_content . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$row['uid'] . $this->enableFields($table), '', '', '1'); - $olrow = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); - $this->versionOL($table, $olrow); - // Merge record content by traversing all fields: - if (is_array($olrow)) { - if (isset($olrow['_ORIG_uid'])) { - $row['_ORIG_uid'] = $olrow['_ORIG_uid']; - } - if (isset($olrow['_ORIG_pid'])) { - $row['_ORIG_pid'] = $olrow['_ORIG_pid']; - } - foreach ($row as $fN => $fV) { - if ($fN !== 'uid' && $fN !== 'pid' && isset($olrow[$fN])) { - if ($this->shouldFieldBeOverlaid($table, $fN, $olrow[$fN])) { - $row[$fN] = $olrow[$fN]; - } - } elseif ($fN === 'uid') { - $row['_LOCALIZED_UID'] = $olrow['uid']; - } - } - } elseif ($OLmode === 'hideNonTranslated' && (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) { - // Unset, if non-translated records should be hidden. ONLY done if the source - // record really is default language and not [All] in which case it is allowed. - unset($row); - } - } elseif ($sys_language_content != $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]) { - unset($row); - } - } else { - // When default language is displayed, we never want to return a record carrying - // another language! - if ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) { - unset($row); - } - } - } - } - } - if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'])) { - foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] as $classRef) { - $hookObject = GeneralUtility::getUserObj($classRef); - if (!$hookObject instanceof PageRepositoryGetRecordOverlayHookInterface) { - throw new \UnexpectedValueException($classRef . ' must implement interface ' . PageRepositoryGetRecordOverlayHookInterface::class, 1269881659); - } - $hookObject->getRecordOverlay_postProcess($table, $row, $sys_language_content, $OLmode, $this); - } - } - return $row; - } + public function getRecordOverlay($table, $row, $sys_language_content, $OLmode = '') { + GeneralUtility::logDeprecatedFunction(); + return $this->getRecordOverlayWithoutFallback($table, $row, $sys_language_content, $OLmode); + } + + /** + * Creates a language overlay for records stored inside tables which contain the + * translation information themselves. + * + * + * @param string $table Table name the record comes from + * @param array $row Record to overlay. Must contain the columns uid, pid and $TCA[$table]['ctrl']['languageField'] + * @param int $sys_language_content Pointer to the sys_language uid to use for content. + * @param string $OLmode Overlay mode. If "hideNonTranslated" then records without translation will not be returned translated but unset (and return value is FALSE) + * @throws \InvalidArgumentException + * @throws \UnexpectedValueException + * @return mixed Returns the input record, possibly overlaid with a translation. But if $OLmode is "hideNonTranslated" then it will return NULL if no translation is found. + */ + public function getRecordOverlayWithoutFallback($table, $row, $sys_language_content, $OLmode) { + if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'])) { + foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] as $classRef) { + $hookObject = GeneralUtility::getUserObj($classRef); + if (!$hookObject instanceof PageRepositoryGetRecordOverlayHookInterface) { + throw new \UnexpectedValueException('$hookObject must implement interface ' . PageRepositoryGetRecordOverlayHookInterface::class, 1269881658); + } + $hookObject->getRecordOverlay_preProcess($table, $row, $sys_language_content, $OLmode, $this); + } + } + if ($row['uid'] > 0 && ($row['pid'] > 0 || in_array($table, $this->tableNamesAllowedOnRootLevel, TRUE))) { + if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) { + if (!$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) { + // Will not be able to work with other tables (Just didn't implement it yet; + // Requires a scan over all tables [ctrl] part for first FIND the table that + // carries localization information for this table (which could even be more + // than a single table) and then use that. Could be implemented, but obviously + // takes a little more....) Will try to overlay a record only if the + // sys_language_content value is larger than zero. + if ($sys_language_content > 0) { + // Must be default language or [All], otherwise no overlaying: + if ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] <= 0) { + // Select overlay record: + $res = $this->getDatabaseConnection()->exec_SELECTquery('*', $table, 'pid=' . (int)$row['pid'] . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$sys_language_content . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$row['uid'] . $this->enableFields($table), '', '', '1'); + $olrow = $this->getDatabaseConnection()->sql_fetch_assoc($res); + $this->getDatabaseConnection()->sql_free_result($res); + $this->versionOL($table, $olrow); + // Merge record content by traversing all fields: + if (is_array($olrow)) { + if (isset($olrow['_ORIG_uid'])) { + $row['_ORIG_uid'] = $olrow['_ORIG_uid']; + } + if (isset($olrow['_ORIG_pid'])) { + $row['_ORIG_pid'] = $olrow['_ORIG_pid']; + } + foreach ($row as $fN => $fV) { + if ($fN !== 'uid' && $fN !== 'pid' && isset($olrow[$fN])) { + if ($this->shouldFieldBeOverlaid($table, $fN, $olrow[$fN])) { + $row[$fN] = $olrow[$fN]; + } + } elseif ($fN === 'uid') { + $row['_LOCALIZED_UID'] = $olrow['uid']; + } + } + } elseif ($OLmode === 'hideNonTranslated' && (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) { + // Unset, if non-translated records should be hidden. ONLY done if the source + // record really is default language and not [All] in which case it is allowed. + $row = NULL; + } + } elseif ($sys_language_content != $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]) { + $row = NULL; + } + } else { + // When default language is displayed, we never want to return a record carrying + // another language! + if ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) { + $row = NULL; + } + } + } + } + } + if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'])) { + foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] as $classRef) { + $hookObject = GeneralUtility::getUserObj($classRef); + if (!$hookObject instanceof PageRepositoryGetRecordOverlayHookInterface) { + throw new \UnexpectedValueException('$hookObject must implement interface ' . PageRepositoryGetRecordOverlayHookInterface::class, 1269881659); + } + $hookObject->getRecordOverlay_postProcess($table, $row, $sys_language_content, $OLmode, $this); + } + } + + return $row; + } /************************************************ * diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php index 5d8f550..f139e3b 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php @@ -98,8 +98,8 @@ class AbstractMenuContentObjectTest extends \TYPO3\CMS\Core\Tests\UnitTestCase $this->subject->sys_page->expects($this->once())->method('getPage')->will($this->returnValue(['_PAGES_OVERLAY_LANGUAGE' => 1])); $this->subject->parent_cObj->expects($this->once())->method('exec_getQuery')->will($this->returnValue(1)); $GLOBALS['TYPO3_DB']->expects($this->exactly(2))->method('sql_fetch_assoc')->will($this->onConsecutiveCalls($this->returnValue(['uid' => 0, 'header' => 'NOT_OVERLAID']), $this->returnValue(false))); - $this->subject->sys_page->expects($this->once())->method('getRecordOverlay')->will($this->returnValue(['uid' => 0, 'header' => 'OVERLAID'])); - $result = $this->subject->_call('sectionIndex', 'field'); + $this->subject->sys_page->expects($this->once())->method('getRecordOverlayWithFallback')->will($this->returnValue(array('uid' => 0, 'header' => 'OVERLAID'))); + $result = $this->subject->_call('sectionIndex', 'field'); $this->assertEquals($result[0]['title'], 'OVERLAID'); } -- 2.7.4 (Apple Git-66)