diff --git a/Classes/Constants.php b/Classes/Constants.php index e9f6d6d3..541f28e0 100644 --- a/Classes/Constants.php +++ b/Classes/Constants.php @@ -17,9 +17,5 @@ class Constants public const L10NMGR_CONFIGURATION_INCLUDE = 3; - public const L10NMGR_LANGUAGE_RESTRICTION_FOREIGN_TABLENAME = 'sys_language'; - - public const L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME = 'sys_language_l10nmgr_language_restricted_record_mm'; - public const L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME = 'l10nmgr_language_restriction'; } diff --git a/Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php b/Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php index 194cff8f..b2395ad0 100644 --- a/Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php +++ b/Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php @@ -17,9 +17,10 @@ * The TYPO3 project - inspiring people to share! */ +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\Exception as DBALDriverException; use Localizationteam\L10nmgr\Constants; use PDO; -use RuntimeException; use SplDoublyLinkedList; use TYPO3\CMS\Core\Collection\AbstractRecordCollection; use TYPO3\CMS\Core\Collection\CollectionInterface; @@ -27,6 +28,11 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; +use TYPO3\CMS\Core\Exception\SiteNotFoundException; +use TYPO3\CMS\Core\Site\Entity\NullSite; +use TYPO3\CMS\Core\Site\Entity\Site; +use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -35,97 +41,40 @@ class LanguageRestrictionCollection extends AbstractRecordCollection implements EditableCollectionInterface { /** - * The table name collections are stored to + * The table name collections are stored to, must be defined in the subclass * * @var string */ - protected static $storageTableName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FOREIGN_TABLENAME; + protected static $storageTableName = 'pages'; /** - * Name of the language-restrictions-relation field (used in the MM_match_fields/fieldname property of the TCA) + * Contrary to the originally idea of collections, we do not load a record from the database here. + * Instead we get the language by its ID. This is the key for our restriction collection * - * @var string - */ - protected string $relationFieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME; - - /** - * Creates this object. - * - * @param string|null $tableName Name of the table to be working on - * @param string|null $fieldName Name of the field where the language restriction relations are defined - * @throws RuntimeException - */ - public function __construct(string $tableName = null, string $fieldName = null) - { - parent::__construct(); - if (!empty($tableName)) { - $this->setItemTableName($tableName); - } elseif (empty($this->itemTableName)) { - throw new RuntimeException(self::class . ' needs a valid itemTableName.', 1341826168); - } - if (!empty($fieldName)) { - $this->setRelationFieldName($fieldName); - } - } - - /** - * Loads the collections with the given id from persistence - * For memory reasons, per default only f.e. title, database-table, - * identifier (what ever static data is defined) is loaded. - * Entries can be load on first access. - * - * @param int $id Id of database record to be loaded + * @param int $languageId Id of the language to be loaded * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections * @param string $tableName Name of table from which entries should be loaded - * @param string $fieldName Name of the language restrictions relation field + * @param string $pageId ID of the page * @return CollectionInterface * @throws \Doctrine\DBAL\DBALException */ - public static function load($id, $fillItems = false, string $tableName = '', string $fieldName = ''): CollectionInterface + public static function load($languageId, $fillItems = false, string $tableName = '', int $pageId = 0): CollectionInterface { - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable(static::$storageTableName); - $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); - - $collectionRecord = $queryBuilder->select('*') - ->from(static::$storageTableName) - ->where( - $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, PDO::PARAM_INT)) - ) - ->setMaxResults(1) - ->execute() - ->fetch(); + try { + $language = self::getLanguage($pageId, $languageId); + $collectionRecord['uid'] = $language->getLanguageId(); + $collectionRecord['title'] = $language->getTitle(); + } catch (\RuntimeException $exception) { + $collectionRecord['uid'] = 0; + $collectionRecord['title'] = ''; + } + $collectionRecord['description'] = 'Restriction Collection'; $collectionRecord['table_name'] = $tableName; - $collectionRecord['field_name'] = $fieldName; return self::create($collectionRecord, $fillItems); } - /** - * Creates a new collection objects and reconstitutes the - * given database record to the new object. - * - * @param array $collectionRecord Database record - * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections - * @return LanguageRestrictionCollection - */ - public static function create(array $collectionRecord, $fillItems = false): LanguageRestrictionCollection - { - /** @var LanguageRestrictionCollection $collection */ - $collection = GeneralUtility::makeInstance( - self::class, - $collectionRecord['table_name'], - $collectionRecord['field_name'] - ); - $collection->fromArray($collectionRecord); - if ($fillItems) { - $collection->loadContents(); - } - return $collection; - } - /** * Populates the content-entries of the storage * Queries the underlying storage for entries of the collection @@ -133,6 +82,8 @@ public static function create(array $collectionRecord, $fillItems = false): Lang * If the content entries of the storage had not been loaded on creation * ($fillItems = false) this function is to be used for loading the contents * afterwards. + * + * @throws DBALException|DBALDriverException */ public function loadContents() { @@ -144,96 +95,27 @@ public function loadContents() } /** - * Gets the collected records in this collection, by - * using . + * Gets the collected records in this collection * * @return array - * @throws \Doctrine\DBAL\DBALException + * @throws DBALException|DBALDriverException */ protected function getCollectedRecords(): array - { - $relatedRecords = []; - - $queryBuilder = $this->getCollectedRecordsQueryBuilder(); - $result = $queryBuilder->execute(); - - while ($record = $result->fetch()) { - $relatedRecords[] = $record; - } - - return $relatedRecords; - } - - /** - * Selects the collected records in this collection, by - * looking up the MM relations of this record to the - * table name defined in the local field 'table_name'. - * - * @return QueryBuilder - */ - protected function getCollectedRecordsQueryBuilder(): QueryBuilder { /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable(static::$storageTableName); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::getCollectionDatabaseTable()); $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); - $queryBuilder->select($this->getItemTableName() . '.*') - ->from(static::$storageTableName) - ->join( - static::$storageTableName, - Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME, - Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME, - $queryBuilder->expr()->eq( - 'sys_language_l10nmgr_language_restricted_record_mm.uid_local', - $queryBuilder->quoteIdentifier(static::$storageTableName . '.uid') - ) - ) - ->join( - Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME, - $this->getItemTableName(), - $this->getItemTableName(), - $queryBuilder->expr()->eq( - Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME . '.uid_foreign', - $queryBuilder->quoteIdentifier($this->getItemTableName() . '.uid') - ) - ) + $result = $queryBuilder->select('*') + ->from(self::getCollectionDatabaseTable()) ->where( - $queryBuilder->expr()->eq( - static::$storageTableName . '.uid', - $queryBuilder->createNamedParameter($this->getIdentifier(), PDO::PARAM_INT) - ), - $queryBuilder->expr()->eq( - Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME . '.tablenames', - $queryBuilder->createNamedParameter($this->getItemTableName()) - ), - $queryBuilder->expr()->eq( - Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME . '.fieldname', - $queryBuilder->createNamedParameter($this->getRelationFieldName()) + $queryBuilder->expr()->inSet( + Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME, + $queryBuilder->createNamedParameter($this->uid, PDO::PARAM_INT) ) - ); + )->execute(); - return $queryBuilder; - } - - /** - * Gets the name of the language restrictions relation field - * - * @return string - */ - public function getRelationFieldName(): string - { - return $this->relationFieldName; - } - - /** - * Sets the name of the language restrictions relation field - * - * @param string $field - */ - public function setRelationFieldName(string $field) - { - $this->relationFieldName = $field; + return $result->fetchAllAssociative(); } /** @@ -255,26 +137,6 @@ public function add($data) $this->storage->push($data); } - /** - * Getter for the storage table name - * - * @return string - */ - public static function getStorageTableName(): string - { - return self::$storageTableName; - } - - /** - * Getter for the storage items field - * - * @return string - */ - public static function getStorageItemsField(): string - { - return self::$storageItemsField; - } - /** * Adds a set of entries to the collection * @@ -350,4 +212,29 @@ protected function getPersistableDataArray(): array 'items' => $this->getItemUidList(), ]; } + + /** + * @param int $pageId + * @return Site|NullSite + */ + protected static function getSiteByPageId(int $pageId) + { + try { + $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId); + } catch (SiteNotFoundException $exception) { + $site = new NullSite(); + } + + return $site; + } + + protected static function getLanguage(int $pageId, $languageId): SiteLanguage + { + $site = self::getSiteByPageId($pageId); + if ($site instanceof Site) { + return $site->getLanguageById($languageId); + } + + throw new \RuntimeException(); + } } diff --git a/Classes/LanguageRestriction/LanguageRestrictionRegistry.php b/Classes/LanguageRestriction/LanguageRestrictionRegistry.php index dec200f0..79b063c3 100644 --- a/Classes/LanguageRestriction/LanguageRestrictionRegistry.php +++ b/Classes/LanguageRestriction/LanguageRestrictionRegistry.php @@ -1,27 +1,26 @@ template = str_repeat(PHP_EOL, 3) . 'CREATE TABLE %s (' . PHP_EOL - . ' %s int(11) DEFAULT \'0\' NOT NULL' . PHP_EOL . ');' . str_repeat(PHP_EOL, 3); + . ' %s text ' . PHP_EOL . ');' . str_repeat(PHP_EOL, 3); } /** * Returns a class instance * - * @return object + * @return LanguageRestrictionRegistry */ - public static function getInstance(): object + public static function getInstance(): LanguageRestrictionRegistry { return GeneralUtility::makeInstance(__CLASS__); } @@ -239,13 +238,13 @@ protected function addTcaColumn(string $tableName, string $fieldName, array $opt $exclude = (bool)$options['exclude']; } - $fieldConfiguration = empty($options['fieldConfiguration']) ? [] : $options['fieldConfiguration']; + $fieldConfiguration = $options['fieldConfiguration'] ?? []; $columns = [ $fieldName => [ 'exclude' => $exclude, 'label' => $label, - 'config' => static::getTcaFieldConfiguration($tableName, $fieldName, $fieldConfiguration), + 'config' => static::getTcaFieldConfiguration($fieldConfiguration), ], ]; @@ -284,33 +283,20 @@ protected function addTcaColumn(string $tableName, string $fieldName, array $opt * @return array * @api */ - public static function getTcaFieldConfiguration( - string $tableName, - string $fieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME, - array $fieldConfigurationOverride = [] - ): array { + public static function getTcaFieldConfiguration(array $fieldConfigurationOverride = []): array { // Forges a new field, default name is "l10nmgr_language_restriction" $fieldConfiguration = [ 'type' => 'select', 'renderType' => 'selectMultipleSideBySide', - 'foreign_table' => Constants::L10NMGR_LANGUAGE_RESTRICTION_FOREIGN_TABLENAME, - 'foreign_table_where' => ' ORDER BY sys_language.sorting ASC', - 'MM' => Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME, - 'MM_opposite_field' => 'items', - 'MM_match_fields' => [ - 'tablenames' => $tableName, - 'fieldname' => $fieldName, - ], - 'size' => 10, + 'itemsProcFunc' => LanguageRestrictionRegistry::class . '->populateAvailableSiteLanguages', 'maxitems' => 9999, ]; // Merge changes to TCA configuration + // TODO: The empty() check can be removed as the mergeRecursiveWithOverrule() takes care in case of an empty + // TODO: overrule array. if (!empty($fieldConfigurationOverride)) { - ArrayUtility::mergeRecursiveWithOverrule( - $fieldConfiguration, - $fieldConfigurationOverride - ); + ArrayUtility::mergeRecursiveWithOverrule($fieldConfiguration, $fieldConfigurationOverride); } return $fieldConfiguration; @@ -407,6 +393,60 @@ public function getDatabaseTableDefinition(string $extensionKey): string return $sql; } + + /** + * Provides a list of all languages available for ALL sites. + * In case no site configuration can be found in the system, + * a fallback is used to add at least the default language. + * + * Used by be_users and be_groups for their `allowed_languages` column. + */ + public function populateAvailableSiteLanguages(array &$fieldInformation): void + { + $allLanguages = []; + foreach ($this->getAllSites() as $site) { + foreach ($site->getAllLanguages() as $language) { + $languageId = $language->getLanguageId(); + if (isset($allLanguages[$languageId])) { + // Language already provided by another site, just add the label separately + $allLanguages[$languageId]['label'] .= ', ' . $language->getTitle() . ' [Site: ' . $site->getIdentifier() . ']'; + continue; + } + $allLanguages[$languageId] = [ + 'label' => $language->getTitle() . ' [Site: ' . $site->getIdentifier() . ']', + 'value' => $languageId, + 'icon' => $language->getFlagIdentifier(), + ]; + } + } + + if ($allLanguages !== []) { + ksort($allLanguages); + unset($allLanguages[0]); + foreach ($allLanguages as $item) { + $fieldInformation['items'][] = [$item['label'], $item['value'], $item['icon']]; + } + return; + } + + // Fallback if no site configuration exists + $recordPid = (int)($fieldInformation['row']['pid'] ?? 0); + $languages = (new NullSite())->getAvailableLanguages($this->getBackendUser(), false, $recordPid); + + foreach ($languages as $languageId => $language) { + $fieldInformation['items'][] = [ + 'label' => $language->getTitle(), + 'value' => $languageId, + 'icon' => $language->getFlagIdentifier(), + ]; + } + } + + protected function getAllSites(): array + { + return GeneralUtility::makeInstance(SiteFinder::class)->getAllSites(); + } + /** * @return LanguageService */ diff --git a/Classes/Model/L10nAccumulatedInformation.php b/Classes/Model/L10nAccumulatedInformation.php index 93b7d509..98c61b3f 100644 --- a/Classes/Model/L10nAccumulatedInformation.php +++ b/Classes/Model/L10nAccumulatedInformation.php @@ -252,8 +252,9 @@ protected function _calculateInternalAccumulatedInformationsArray(): void $sysLang, true, 'pages', - Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME + $pageId, ); + if ($languageIsRestricted->hasItem((int)$pageId)) { $this->excludeIndex['pages:' . $pageId] = 1; continue; @@ -309,7 +310,7 @@ protected function _calculateInternalAccumulatedInformationsArray(): void $sysLang, true, $table, - Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME + $rowUid, ); if ($languageIsRestricted->hasItem($rowUid)) { $this->excludeIndex[$table . ':' . $rowUid] = 1;