Commit 44b5407f authored by Fabian Galinski's avatar Fabian Galinski

Merge branch 'feature_move_visbility_config_to_relational_table' into 'master'

[TASK] Feature move visibility config to relational table

See merge request !5
parents b9f6bab4 df285a2b
......@@ -27,7 +27,7 @@ namespace TYPO3\Languagevisibility\Element;
/**
*
* @author Daniel Poetzinger <poetzinger@aoemedia.de>
* @author Daniel Poetzinger <poetzinger@aoemedia.de>
* @coauthor Tolleiv Nietsch <nietsch@aoemedia.de>
* @coauthor Timo Schmidt <schmidt@aoemedia.de>
*/
......@@ -39,7 +39,7 @@ class ContentElement extends RecordElement {
* (non-PHPdoc)
* @see classes/tx_languagevisibility_element#initialisations()
*/
protected function initialisations() {
protected function initialisations(): void {
}
/**
......
......@@ -26,14 +26,21 @@ namespace TYPO3\Languagevisibility\Element;
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use Doctrine\DBAL\DBALException;
use ReflectionClass;
use ReflectionException;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\Languagevisibility\Exception\InvalidRowException;
use TYPO3\Languagevisibility\Manager\CacheManager;
use TYPO3\Languagevisibility\Repository\VisibilityFlagRepository;
/**
* Class tx_languagevisibility_element
......@@ -53,24 +60,23 @@ abstract class Element {
protected $row = [];
/**
* This array holds the global visibility settings (from the 'tx_languagevisibility_visibility' field of the default language record)
* This array holds the global visibility settings
*
* @var array
*/
protected $globalVisibilitySettings = [];
/**
* @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
* @var VariableFrontend
*/
protected $cache;
/**
* @param array $row
* @param string $tablename
* @return Element
* @throws \ReflectionException
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
* @throws InvalidRowException
* @throws ReflectionException
* @throws NoSuchCacheException
* @throws InvalidRowException|DBALException
*/
public function __construct(array $row, string $tablename = '') {
$this->table = $tablename;
......@@ -90,11 +96,11 @@ abstract class Element {
$this->globalVisibilitySettings = $this->getCache()->get($cacheKey);
} elseif ($cacheKey) {
$globalVisibilitySettings = $this->fetchGlobalVisibilitySettings();
$this->globalVisibilitySettings = @unserialize($globalVisibilitySettings, ['allowed_classes' => FALSE]);
$this->globalVisibilitySettings = $globalVisibilitySettings;
$this->getCache()->set($cacheKey, $this->globalVisibilitySettings);
} else {
$globalVisibilitySettings = $this->fetchGlobalVisibilitySettings();
$this->globalVisibilitySettings = @unserialize($globalVisibilitySettings, ['allowed_classes' => FALSE]);
$this->globalVisibilitySettings = $globalVisibilitySettings;
}
$this->initialisations();
......@@ -106,7 +112,7 @@ abstract class Element {
* @param string $table
* @return void
*/
public function setTable(string $table) {
public function setTable(string $table): void {
$this->table = $table;
}
......@@ -120,35 +126,23 @@ abstract class Element {
}
/**
* @return array
*/
public function getRow(): array {
return $this->row;
}
/**
* @param array $row
*/
public function setRow(array $row): void {
$this->row = $row;
}
/**
* Method to deternmine that an Element will not be instanciated with
* Method to determine that an Element will not be instantiated with
* data of an overlay.
*
* @param array $row
* @return bool
*/
protected function isRowOriginal(array $row) {
protected function isRowOriginal(array $row): bool {
return !isset($row[$GLOBALS['TCA'][$this->table]['ctrl']['transOrigPointerField']]) ||
$row[$GLOBALS['TCA'][$this->table]['ctrl']['transOrigPointerField']] === 0;
}
/**
* possibility to add inits in subclasses
*
* @return void
**/
protected function initialisations() {
protected function initialisations(): void {
}
/**
......@@ -199,18 +193,22 @@ abstract class Element {
public function getInformativeDescription(): string {
if ($this->isMonolithicTranslated()) {
return LocalizationUtility::translate('backend.contentElement.notDefault', 'languagevisibility');
} elseif ($this->isLanguageSetToAll()) {
}
if ($this->isLanguageSetToAll()) {
return LocalizationUtility::translate('backend.contentElement.languageSetToAll', 'languagevisibility');
} elseif ($this->isLanguageSetToDefault()) {
}
if ($this->isLanguageSetToDefault()) {
return LocalizationUtility::translate('backend.contentElement.normal', 'languagevisibility');
} else {
return LocalizationUtility::translate('backend.contentElement.translated', 'languagevisibility');
}
return LocalizationUtility::translate('backend.contentElement.translated', 'languagevisibility');
}
/**
* This method is used to determine the visibility of the element. Technically it merges the visibility of
* the default language record and the overlay record and returns the visibility. The visibility in the overlayrecord
* the default language record and the overlay record and returns the visibility. The visibility in the overlayRecord
* can overwrite the visibility of its own language.
* This method is only need to display the visibility setting in the backend.
*
......@@ -241,7 +239,6 @@ abstract class Element {
* This method is only need to display the visibility setting in the backend.
*
* @param int $languageid
* @param int $languageid
* @return string
* @return string|null
* @deprecated
......@@ -283,32 +280,46 @@ abstract class Element {
/**
* fetches and sets the global / default language visibility settings for the element
*
* @return string|null
* @return array
* @throws DBALException
*/
public function fetchGlobalVisibilitySettings(): ?string {
// if the element is not in default language, we want to get the default language element
if (!$this->isLanguageSetToDefault() && !$this->isOrigElement() && $this->getOrigElementUid() > 0) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$row = $queryBuilder
->select('*')
->from($this->table)
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter(
$this->getOrigElementUid(), \PDO::PARAM_INT
)
)
)
->setMaxResults(1)
->execute()->fetch();
$globalVisibilitySettings = $row['tx_languagevisibility_visibility'];
public function fetchGlobalVisibilitySettings(): array {
$globalVisibilitySettings = [];
$table = $this->getTable();
$visibilityFlagRepository = GeneralUtility::makeInstance(VisibilityFlagRepository::class);
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
if ($table === 'pages') {
$pid = $this->getUid();
} else {
$globalVisibilitySettings = $this->row['tx_languagevisibility_visibility'];
$pid = $this->getPid();
}
try {
$rootLine = BackendUtility::BEgetRootLine($pid);
$site = $siteFinder->getSiteByPageId($pid, $rootLine);
} catch (\Exception $e) {
return [];
}
$availablesLanguages = $site->getAllLanguages();
foreach ($availablesLanguages as $language) {
$flagRow = $visibilityFlagRepository->getVisibilityFlag(
$table, $this->getUid(), $language->getLanguageId(), TRUE
);
if ($flagRow === FALSE || $flagRow === NULL) {
$flag = 'active';
} else {
$flag = $flagRow['flag'];
}
$globalVisibilitySettings[$language->getLanguageId()] = $flag;
}
return $globalVisibilitySettings;
......@@ -317,11 +328,11 @@ abstract class Element {
/**
* Receive relevant fallbackOrder
*
* @param Language $language
* @param SiteLanguage $language
* @return array
*/
public function getFallbackOrder(Language $language): array {
return $language->getFallbackOrder($this);
public function getFallbackOrder(SiteLanguage $language): array {
return $language->getFallbackLanguageIds();
}
/**
......@@ -343,7 +354,7 @@ abstract class Element {
}
/**
* Checks if the current record is set to language all (that is typically used to indicate that per default this element is visible in all langauges)
* Checks if the current record is set to language all (that is typically used to indicate that per default this element is visible in all languages)
*
* @return bool
*/
......@@ -363,11 +374,11 @@ abstract class Element {
/**
* Compare element-language and foreign language.
*
* @param Language $language
* @param SiteLanguage $language
* @return bool
*/
public function languageEquals(Language $language): bool {
return (int) $this->row['sys_language_uid'] === $language->getUid();
public function languageEquals(SiteLanguage $language): bool {
return (int) $this->row['sys_language_uid'] === $language->getLanguageId();
}
/**
......@@ -381,14 +392,10 @@ abstract class Element {
$result = FALSE;
if (!is_numeric($languageid)) {
$result = FALSE;
} else {
if ($languageid === 0) {
$result = TRUE;
} else {
if ($this->_hasOverlayRecordForLanguage($languageid)) {
$result = TRUE;
}
}
} else if ($languageid === 0) {
$result = TRUE;
} else if ($this->_hasOverlayRecordForLanguage($languageid)) {
$result = TRUE;
}
return $result;
......@@ -415,7 +422,7 @@ abstract class Element {
}
/**
* Method to get a short description of the elementtype.
* Method to get a short description of the elementType.
* An extending class should overwrite this method.
*
* @return string
......@@ -424,20 +431,11 @@ abstract class Element {
return 'TYPO3 Element';
}
/**
* By default no element supports inheritance
*
* @return bool
*/
public function supportsInheritance(): bool {
return FALSE;
}
/**
* Gets the cache
*
* @return VariableFrontend
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
* @throws NoSuchCacheException
*/
protected function getCache(): VariableFrontend {
if (!$this->cache) {
......@@ -479,7 +477,7 @@ abstract class Element {
*
* @param array $row
* @return bool
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
* @throws AspectNotFoundException
*/
protected function getEnableFieldResult(array $row): bool {
$ctrl = $GLOBALS['TCA'][$this->table]['ctrl'];
......
......@@ -26,15 +26,14 @@ namespace TYPO3\Languagevisibility\Element;
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\Languagevisibility\Manager\CacheManager;
use TYPO3\Languagevisibility\Service\FrontendServices;
use TYPO3\Languagevisibility\Exception\TableNotSupportedException;
/**
* Class tx_languagevisibility_elementFactory
* Class ElementFactory
*/
class ElementFactory {
......@@ -43,13 +42,13 @@ class ElementFactory {
*
* @param string $table table
* @param array $row identifier
* @param bool $overlay_ids boolean parameter to overlay uids if the user is in workspace context
* @throws \UnexpectedValueException
* @param bool $overlay_ids boolean parameter to overlay UIDs if the user is in workspace context
* @return Element
* @throws TableNotSupportedException
*/
public function getElementForTable(string $table, array $row, bool $overlay_ids = TRUE): Element {
if (!FrontendServices::isSupportedTable($table)) {
throw new \UnexpectedValueException($table . ' not supported ', 1195039394);
throw new TableNotSupportedException($table . ' not supported ', 1195039394);
}
if (
......@@ -94,7 +93,7 @@ class ElementFactory {
} elseif (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['languagevisibility']['recordElementSupportedTables'][$table])) {
$element = $this->getElementInstance(RecordElement::class, $row);
} else {
throw new UnexpectedValueException($table . ' not supported ', 1195039394);
throw new TableNotSupportedException($table . ' not supported ', 1195039394);
}
break;
}
......@@ -104,85 +103,11 @@ class ElementFactory {
return $element;
}
/**
* This method is used to retrieve all parent elements (an parent elements needs to
* have the flag 'tx_languagevisibility_inheritanceflag_original' or
* needs to be orverlayed with a record, that has the field 'tx_languagevisibility_inheritanceflag_overlayed'
* configured or is the first element of the rootline
*
* @param Element $element
* @param Language $language
* @return array $elements (collection of tx_languagevisibility_element)
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
*/
public function getParentElementsFromElement(Element $element, $language) {
$parentPage = $this->getElementForTable('pages', $element->getRow());
return $this->getParentElementsFromElement($parentPage, $language);
}
/**
* This method is needed because the getRootline method from t3lib_pageSelect causes an error when
* getRootline is called be cause getRootline internally uses languagevisibility to determine the
* visibility during the rootline calculation. This results in an unlimited recursion.
*
* @todo The rooline can be build in a smarter way, once the rootline for a page has been created
* same parts of the rootline not have to be calculated twice.
* @param $uid
* @param $languageid
* @return array
* @internal param \The $integer page uid for which to seek back to the page tree root.
*/
protected function getOverlayedRootLine($uid, $languageid) {
$cacheManager = CacheManager::getInstance();
$cacheData = $cacheManager->get('overlayedRootline');
$isCacheEnabled = CacheManager::isCacheEnabled();
if (!$isCacheEnabled || !isset($cacheData[$uid][$languageid])) {
$uid = (int) $uid;
// Initialize:
$selFields = GeneralUtility::uniqueList('pid,uid,t3ver_oid,t3ver_wsid,t3ver_state,title,alias,nav_title,media,layout,hidden,starttime,endtime,fe_group,extendToSubpages,doktype,TSconfig,is_siteroot,mount_pid,mount_pid_ol,fe_login_mode,' . $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields']);
$loopCheck = 0;
$theRowArray = Array();
while ($uid !== 0 && $loopCheck < 20) { // Max 20 levels in the page tree.
$row = BackendUtility::getRecordWSOL('pages', $uid, $selFields, ' AND pages.doktype!=255');
if (!$row) {
// broken rootline
return [];
}
BackendUtility::fixVersioningPid('pages', $row);
if (is_array($row)) {
// Mount Point page types are allowed ONLY a) if they are the outermost record in rootline and b) if the overlay flag is not set:
$uid = $row['pid']; // Next uid
}
// Add row to rootline with language overlaid:
$langvisHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay']['languagevisility'];
unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay']['languagevisility']);
$theRowArray[] = BackendUtility::getRecordLocalization('pages', $uid, $languageid);
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPageOverlay']['languagevisility'] = $langvisHook;
$loopCheck++;
}
// Create output array (with reversed order of numeric keys):
$output = Array();
$c = count($theRowArray);
foreach ($theRowArray as $key => $val) {
$c--;
$output[$c] = $val;
}
$cacheData[$uid][$languageid] = $output;
$cacheManager->set('overlayedRootline', $cacheData);
}
return $cacheData[$uid][$languageid];
}
/**
* Gets instance depending on TYPO3 version
*
* @param string $name name of the class
* @param array $row row that is used to initialaze element instance
* @param array $row row that is used to initialize element instance
* @return Object
*/
private function getElementInstance($name, $row) {
......
<?php
namespace TYPO3\Languagevisibility\Element;
/***************************************************************
* Copyright notice
*
* (c) 2007 AOE media (dev@aoemedia.de)
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Backend\Utility\IconUtility;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
*
* @author Daniel Poetzinger <poetzinger@aoemedia.de>
* @coauthor Tolleiv Nietsch <nietsch@aoemedia.de>
* @coauthor Timo Schmidt <schmidt@aoemedia.de>
*/
class Language {
/**
* @var array
*/
private $row;
/**
* @var array
*/
protected static $flagCache;
/**
* Holds the exploded fallBackOrderArray
*
* @var array
*/
protected $defaultFallBackOrderArray;
/**
* Holds the exploded elementFallBackOrderArray
*
* @var array
*/
protected $elementFallBackOrderArray;
/**
* @var string holds the lg_iso_2 isocode
*/
protected $lg_iso_2;
/**
* @param array $row
*/
public function setData(array $row) {
$this->row = $row;
}
/**
* Returns the fallback order of this language as array.
*
* @param Element $contextElement
* @return array
*/
public function getFallbackOrder(Element $contextElement): array {
// determine and explode only once
if (!isset($this->defaultFallBackOrderArray)) {
// unfortunatly defaultlangauge is 999 instead of 0 (reason in formrendering of typo3):
$tx_languagevisibility_fallbackorder = str_replace(
'999', '0', $this->row['tx_languagevisibility_fallbackorder']
);
$this->defaultFallBackOrderArray = GeneralUtility::trimExplode(',', $tx_languagevisibility_fallbackorder);
}
return $this->triggerFallbackHooks('getFallbackOrder', $this->defaultFallBackOrderArray, $contextElement);
}
/**
* Returns the fallback order for this language for elements
*
* @param Element $contextElement
* @return array
*/
public function getFallbackOrderElement(Element $contextElement): array {
// determine and explode only once
if (!isset($this->elementFallBackOrderArray)) {
if ($this->usesComplexFallbackSettings()) {
$tx_languagevisibility_fallbackorderel = str_replace(
'999', '0', $this->row['tx_languagevisibility_fallbackorderel']
);
$this->elementFallBackOrderArray = GeneralUtility::trimExplode(
',', $tx_languagevisibility_fallbackorderel
);
} else {
$this->elementFallBackOrderArray = $this->getFallbackOrder($contextElement);
}
}
return $this->triggerFallbackHooks(
'getFallbackOrderElement', $this->elementFallBackOrderArray, $contextElement
);
}
/**
*
* @param string $key
* @param array $fallbackorder
* @param Element $contextElement
* @return array
*/
protected function triggerFallbackHooks($key, $fallbackorder, Element $contextElement): array {
$result = [
'priority' => 10,
'fallbackorder' => $fallbackorder,
];
$fallback = $result;
if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['languagevisibility'][$key])) {
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['languagevisibility'][$key] as $classRef) {
$hookObj = GeneralUtility::makeInstance($classRef);
if (method_exists($hookObj, $key)) {
$result = $hookObj->$key($this, $fallback, $contextElement);
if ($result['priority'] > $fallback['priority']) {
$fallback = $result;
}
}
}
}
return $fallback['fallbackorder'];
}
/**