Commit 3e4e030f authored by Benni Mack's avatar Benni Mack Committed by Christian Kuhn
Browse files

[TASK] Use BitSet class for pages.l18n_cfg

This also deprecates two GeneralUtility methods which
do not belong in this class, and makes the API more
understandable and better documented.

Resolves: #92551
Releases: master
Change-Id: Ibb8b02ae11c76f3f7f65bf0a3eb22c82e94127e5
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65749


Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 7b8170cd
......@@ -33,6 +33,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Error\Http\ShortcutTargetPageNotFoundException;
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Versioning\VersionState;
......@@ -477,10 +478,11 @@ class PageRepository implements LoggerAwareInterface
$languageUid = $languageAspect->getId();
// Checks if the default language version can be shown
// Block page is set, if l18n_cfg allows plus: 1) Either default language or 2) another language but NO overlay record set for page!
if (GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg']) && (!$languageUid || $languageUid && !$page['_PAGES_OVERLAY'])) {
$pageTranslationVisibility = new PageTranslationVisibility((int)($page['l18n_cfg'] ?? 0));
if ($pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() && (!$languageUid || $languageUid && !$page['_PAGES_OVERLAY'])) {
return false;
}
if ($languageUid > 0 && GeneralUtility::hideIfNotTranslated($page['l18n_cfg'])) {
if ($languageUid > 0 && $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
if (!$page['_PAGES_OVERLAY'] || (int)$page['_PAGES_OVERLAY_LANGUAGE'] !== $languageUid) {
return false;
}
......
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Type\Bitmask;
use TYPO3\CMS\Core\Type\BitSet;
/**
* A class providing constants for bitwise operations on page translation handling
* from $GLOBALS[TYPO3_CONF_VARS][FE][hidePagesIfNotTranslatedByDefault] and pages.l18n_cfg
*
* Side note: The DB field pages.l18n_cfg (bitmask) has l10n_mode=exclude meaning that it
* can only be set on the default language and is automatically mirrored to all translated pages.
*/
final class PageTranslationVisibility extends BitSet
{
private const HIDE_DEFAULT_LANGUAGE = 1;
private const HIDE_TRANSLATION_IF_NO_TRANSLATED_RECORD_EXISTS = 2;
/**
* Due to the nature of the pages table, where you always have to have a page in the default
* language, sometimes a page should be hidden in the default language (e.g. english), but
* only visible in the created and available language=6 (e.g. polish).
* This can be configured via pages.l18n_cfg=1 on a per-page basis.
*
* @return bool whether the page has the flag set
*/
public function shouldBeHiddenInDefaultLanguage(): bool
{
return $this->get(self::HIDE_DEFAULT_LANGUAGE);
}
/**
* Response on input location setting value whether the
* page should be hidden if no translation exists.
*
* Imagine this:
* - You link to page 23 in language=5 (e.g. italian)
* - The page was never translated to language=5 (no pages record with sys_language_uid=5 created)
* => Should the fallback kick in or not?
*
* The answer depends on your use-case (e.g. fallback of italian to english etc) and can
* be tuned via the global configuration option and the pages.l18n_cfg=2 flag.
*
* @return bool true if the page should be hidden
*/
public function shouldHideTranslationIfNoTranslatedRecordExists(): bool
{
return $GLOBALS['TYPO3_CONF_VARS']['FE']['hidePagesIfNotTranslatedByDefault'] xor ($this->get(self::HIDE_TRANSLATION_IF_NO_TRANSLATED_RECORD_EXISTS));
}
}
......@@ -3042,9 +3042,11 @@ class GeneralUtility
*
* @param int $l18n_cfg_fieldValue Value from "l18n_cfg" field of a page record
* @return bool TRUE if the page should be hidden
* @deprecated since TYPO3 v11, will be removed in TYPO3 v12. Use PageTranslationVisibility BitSet instead.
*/
public static function hideIfNotTranslated($l18n_cfg_fieldValue)
{
trigger_error('GeneralUtility::hideIfNotTranslated() will be removed in TYPO3 v12, use the PageTranslationVisibility BitSet API instead.', E_USER_DEPRECATED);
return $GLOBALS['TYPO3_CONF_VARS']['FE']['hidePagesIfNotTranslatedByDefault'] xor ($l18n_cfg_fieldValue & 2);
}
......@@ -3054,9 +3056,11 @@ class GeneralUtility
*
* @param int $localizationConfiguration
* @return bool
* @deprecated since TYPO3 v11, will be removed in TYPO3 v12. Use PageTranslationVisibility BitSet instead.
*/
public static function hideIfDefaultLanguage($localizationConfiguration)
{
trigger_error('GeneralUtility::hideIfDefaultLanguage() will be removed in TYPO3 v12, use the PageTranslationVisibility BitSet API instead.', E_USER_DEPRECATED);
return (bool)($localizationConfiguration & 1);
}
......
.. include:: ../../Includes.txt
===============================================================================
Deprecation: #92551 - GeneralUtility methods related to pages.l18n_cfg behavior
===============================================================================
See :issue:`92551`
Description
===========
The methods
* :php:`GeneralUtility::hideIfNotTranslated()`
* :php:`GeneralUtility::hideIfDefaultLanguage()`
have been deprecated in favor of a new BitSet-based PHP class
:php:`TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility`.
Impact
======
Calling both methods will trigger a PHP deprecation notice.
Affected Installations
======================
TYPO3 installation with custom third-party extensions calling
these methods for explicit and special page translation handling.
Migration
=========
Instead of :php:`GeneralUtility::hideIfDefaultLanguage()` use
.. code-block:: php
$pageTranslationVisibility = new PageTranslationVisibility((int)$page['l18n_cfg'] ?? 0)
$pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()
Instead of :php:`GeneralUtility::hideIfNotTranslated()` use
.. code-block:: php
$pageTranslationVisibility = new PageTranslationVisibility((int)$page['l18n_cfg'] ?? 0)
$pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()
.. index:: PHP-API, FullyScanned, ext:core
......@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Site\Entity\NullSite;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
use TYPO3\CMS\Core\TypoScript\TemplateService;
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -603,8 +604,9 @@ abstract class AbstractMenuContentObject
$lRecs = [];
}
// Checking if the "disabled" state should be set.
if (GeneralUtility::hideIfNotTranslated($tsfe->page['l18n_cfg']) && $sUid &&
empty($lRecs) || GeneralUtility::hideIfDefaultLanguage($tsfe->page['l18n_cfg']) &&
$pageTranslationVisibility = new PageTranslationVisibility((int)($tsfe->page['l18n_cfg'] ?? 0));
if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $sUid &&
empty($lRecs) || $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() &&
(!$sUid || empty($lRecs)) ||
!$this->conf['special.']['normalWhenNoLanguage'] && $sUid && empty($lRecs)
) {
......@@ -1213,7 +1215,8 @@ abstract class AbstractMenuContentObject
}
// Checking if "&L" should be modified so links to non-accessible pages will not happen.
if ($this->getCurrentLanguageAspect()->getId() > 0 && $this->conf['protectLvar']) {
if ($this->conf['protectLvar'] === 'all' || GeneralUtility::hideIfNotTranslated($data['l18n_cfg'])) {
$pageTranslationVisibility = new PageTranslationVisibility((int)($data['l18n_cfg'] ?? 0));
if ($this->conf['protectLvar'] === 'all' || $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
$olRec = $this->sys_page->getPageOverlay($data['uid'], $this->getCurrentLanguageAspect()->getId());
if (empty($olRec)) {
// If no page translation record then page can NOT be accessed in
......@@ -1633,13 +1636,13 @@ abstract class AbstractMenuContentObject
}
// No valid subpage if the default language should be shown and the page settings
// are excluding the visibility of the default language
if (!$languageId && GeneralUtility::hideIfDefaultLanguage($theRec['l18n_cfg'] ?? 0)) {
$pageTranslationVisibility = new PageTranslationVisibility((int)($theRec['l18n_cfg'] ?? 0));
if (!$languageId && $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()) {
continue;
}
// No valid subpage if the alternative language should be shown and the page settings
// are requiring a valid overlay but it doesn't exists
$hideIfNotTranslated = GeneralUtility::hideIfNotTranslated($theRec['l18n_cfg'] ?? null);
if ($languageId && $hideIfNotTranslated && !$theRec['_PAGES_OVERLAY']) {
if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() && $languageId > 0 && !$theRec['_PAGES_OVERLAY']) {
continue;
}
// No valid subpage if the subpage is banned by excludeUidList
......
......@@ -59,6 +59,7 @@ use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
use TYPO3\CMS\Core\TypoScript\TemplateService;
......@@ -1927,6 +1928,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
$languageId = $languageAspect->getId();
$languageContentId = $languageAspect->getContentId();
$pageTranslationVisibility = new PageTranslationVisibility((int)($this->page['l18n_cfg'] ?? 0));
// If sys_language_uid is set to another language than default:
if ($languageAspect->getId() > 0) {
// check whether a shortcut is overwritten by a translated page
......@@ -1937,7 +1939,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
$olRec = $this->sys_page->getPageOverlay($this->id, $languageAspect->getId());
if (empty($olRec)) {
// If requested translation is not available:
if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
if ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
$request,
'Page is not available in the requested language.',
......@@ -2013,7 +2015,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
$this->sys_page = GeneralUtility::makeInstance(PageRepository::class, $this->context);
// If default language is not available:
if ((!$languageAspect->getContentId() || !$languageAspect->getId())
&& GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'] ?? 0)
&& $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()
) {
$message = 'Page is not available in default language.';
$this->logger->error($message);
......
......@@ -35,6 +35,7 @@ use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\RootlineUtility;
......@@ -193,14 +194,15 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
}
// Check if the target page can be access depending on l18n_cfg
if (!$tsfe->sys_page->isPageSuitableForLanguage($page, $languageAspect)) {
if ($siteLanguageOfTargetPage->getLanguageId() === 0 && GeneralUtility::hideIfDefaultLanguage($page['l18n_cfg'])) {
$pageTranslationVisibility = new PageTranslationVisibility((int)($page['l18n_cfg'] ?? 0));
if ($siteLanguageOfTargetPage->getLanguageId() === 0 && $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage()) {
throw new UnableToLinkException('Default language of page "' . $linkDetails['typoLinkParameter'] . '" is hidden, so "' . $linkText . '" was not linked.', 1551621985, null, $linkText);
}
// If the requested language is not the default language and the page has no overlay for this language
// generating a link would cause a 404 error when using this like if one of those conditions apply:
// - The page is set to be hidden if it is not translated (evaluated in TSFE)
// - The site configuration has a "strict" fallback set (evaluated in the Router - very early)
if ($siteLanguageOfTargetPage->getLanguageId() > 0 && !isset($page['_PAGES_OVERLAY']) && (GeneralUtility::hideIfNotTranslated($page['l18n_cfg']) || $siteLanguageOfTargetPage->getFallbackType() === 'strict')) {
if ($siteLanguageOfTargetPage->getLanguageId() > 0 && !isset($page['_PAGES_OVERLAY']) && ($pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() || $siteLanguageOfTargetPage->getFallbackType() === 'strict')) {
throw new UnableToLinkException('Fallback to default language of page "' . $linkDetails['typoLinkParameter'] . '" is disabled, so "' . $linkText . '" was not linked.', 1551621996, null, $linkText);
}
}
......
......@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Type\Bitmask\PageTranslationVisibility;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -196,7 +197,8 @@ class TranslationStatusController
)
) . '" class="btn btn-default" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_viewPage') . '">' .
$this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
$status = GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : 'success';
$pageTranslationVisibility = new PageTranslationVisibility((int)($data['row']['l18n_cfg'] ?? 0));
$status = $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() ? 'danger' : 'success';
// Create links:
$editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
'edit' => [
......@@ -222,8 +224,8 @@ class TranslationStatusController
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editDefaultLanguagePage'
) . '">' . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render() . '</a>';
$info .= '&nbsp;';
$info .= GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.1')) . '">D</span>' : '&nbsp;';
$info .= GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.2')) . '">N</span>' : '&nbsp;';
$info .= $pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.1')) . '">D</span>' : '&nbsp;';
$info .= $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists() ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.2')) . '">N</span>' : '&nbsp;';
// Put into cell:
$tCells[] = '<td class="' . $status . ' col-border-left"><div class="btn-group">' . $info . '</div></td>';
$tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
......@@ -237,9 +239,16 @@ class TranslationStatusController
}
if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === $languageId) {
$row = $this->getLangStatus($data['row']['uid'], $languageId);
if ($pageTranslationVisibility->shouldBeHiddenInDefaultLanguage() || $pageTranslationVisibility->shouldHideTranslationIfNoTranslatedRecordExists()) {
$status = 'danger';
} else {
$status = '';
}
if (is_array($row)) {
$langRecUids[$languageId][] = $row['uid'];
$status = $row['_HIDDEN'] ? (GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '') : 'success';
if (!$row['_HIDDEN']) {
$status = 'success';
}
$icon = $this->iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL)->render();
$info = $icon . ($showPageId ? ' [' . (int)$row['uid'] . ']' : '') . ' ' . htmlspecialchars(
GeneralUtility::fixed_lgd_cs($row['title'], $titleLen)
......@@ -274,7 +283,6 @@ class TranslationStatusController
'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], $languageId) . '</td>';
} else {
$status = GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '';
$info = '<div class="btn-group"><label class="btn btn-default btn-checkbox">';
$info .= '<input type="checkbox" data-lang="' . $languageId . '" data-uid="' . (int)$data['row']['uid'] . '" name="newOL[' . $languageId . '][' . $data['row']['uid'] . ']" value="1" />';
$info .= '<span class="t3-icon fa"></span></label></div>';
......
......@@ -1022,4 +1022,18 @@ return [
'Breaking-91473-DeprecatedFunctionalityRemoved.rst'
],
],
'TYPO3\CMS\Core\Utility\GeneralUtility::hideIfDefaultLanguage' => [
'numberOfMandatoryArguments' => 1,
'maximumNumberOfArguments' => 1,
'restFiles' => [
'Deprecation-92551-GeneralUtilityMethodsRelatedToPagesl18n_cfgBehavior.rst'
],
],
'TYPO3\CMS\Core\Utility\GeneralUtility::hideIfNotTranslated' => [
'numberOfMandatoryArguments' => 1,
'maximumNumberOfArguments' => 1,
'restFiles' => [
'Deprecation-92551-GeneralUtilityMethodsRelatedToPagesl18n_cfgBehavior.rst'
],
],
];
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment