[FEATURE] Make tables restrictable by language to exclude them from exports 97/58697/6
authorCybercraft <info@cybercraft.de>
Thu, 25 Oct 2018 10:51:05 +0000 (12:51 +0200)
committerJo Hasenau <info@cybercraft.de>
Thu, 20 Dec 2018 15:53:25 +0000 (16:53 +0100)
Change-Id: I4187d12ed4676d79f898d8e0308938070cde16c6
Resolves: #82813
Releases: master, 8-0
Reviewed-on: https://review.typo3.org/58697
Reviewed-by: Jo Hasenau <info@cybercraft.de>
Tested-by: Jo Hasenau <info@cybercraft.de>
Classes/Constants.php
Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php [new file with mode: 0644]
Classes/LanguageRestriction/LanguageRestrictionRegistry.php [new file with mode: 0644]
Classes/Model/L10nAccumulatedInformation.php
Classes/Utility/L10nmgrExtensionManagementUtility.php [new file with mode: 0644]
Configuration/TCA/Overrides/pages.php
Configuration/TCA/Overrides/tt_content.php [new file with mode: 0644]
Resources/Private/Language/locallang_db.xlf
ext_localconf.php
ext_tables.sql

index 2ee0fcc..d59ea26 100644 (file)
@@ -13,4 +13,8 @@ class Constants
     const L10NMGR_CONFIGURATION_EXCLUDE = 2;
     const L10NMGR_CONFIGURATION_INCLUDE = 3;
 
-}
\ No newline at end of file
+    const L10NMGR_LANGUAGE_RESTRICTION_FOREIGN_TABLENAME = 'sys_language';
+    const L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME = 'sys_language_l10nmgr_language_restricted_record_mm';
+    const L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME = 'l10nmgr_language_restriction';
+
+}
diff --git a/Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php b/Classes/LanguageRestriction/Collection/LanguageRestrictionCollection.php
new file mode 100644 (file)
index 0000000..87f408c
--- /dev/null
@@ -0,0 +1,331 @@
+<?php
+namespace Localizationteam\L10nmgr\LanguageRestriction\Collection;
+
+/*
+ * 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!
+ */
+
+use Localizationteam\L10nmgr\Constants;
+use TYPO3\CMS\Core\Collection\AbstractRecordCollection;
+use TYPO3\CMS\Core\Collection\CollectionInterface;
+use TYPO3\CMS\Core\Collection\EditableCollectionInterface;
+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\Utility\GeneralUtility;
+
+/**
+ * Language Restriction Collection to handle records attached to a language
+ */
+class LanguageRestrictionCollection extends AbstractRecordCollection implements EditableCollectionInterface
+{
+    /**
+     * The table name collections are stored to
+     *
+     * @var string
+     */
+    protected static $storageTableName = Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME;
+
+    /**
+     * Name of the language-restrictions-relation field (used in the MM_match_fields/fieldname property of the TCA)
+     *
+     * @var string
+     */
+    protected $relationFieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME;
+
+    /**
+     * Creates this object.
+     *
+     * @param string $tableName Name of the table to be working on
+     * @param string $fieldName Name of the field where the language restriction relations are defined
+     * @throws \RuntimeException
+     */
+    public function __construct($tableName = null, $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);
+        }
+    }
+
+    /**
+     * 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)
+    {
+        /** @var $collection LanguageRestrictionCollection */
+        $collection = GeneralUtility::makeInstance(
+            self::class,
+            $collectionRecord['table_name'],
+            $collectionRecord['field_name']
+        );
+        $collection->fromArray($collectionRecord);
+        if ($fillItems) {
+            $collection->loadContents();
+        }
+        return $collection;
+    }
+
+    /**
+     * 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 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
+     * @return CollectionInterface
+     */
+    public static function load($id, $fillItems = false, $tableName = '', $fieldName = '')
+    {
+        $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();
+
+        $collectionRecord['table_name'] = $tableName;
+        $collectionRecord['field_name'] = $fieldName;
+
+        return self::create($collectionRecord, $fillItems);
+    }
+
+    /**
+     * 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 = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable(static::$storageTableName);
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $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')
+                )
+            )
+            ->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(), \PDO::PARAM_STR)
+                ),
+                $queryBuilder->expr()->eq(
+                    Constants::L10NMGR_LANGUAGE_RESTRICTION_MM_TABLENAME . '.fieldname',
+                    $queryBuilder->createNamedParameter($this->getRelationFieldName(), \PDO::PARAM_STR)
+                )
+            );
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Gets the collected records in this collection, by
+     * using <getCollectedRecordsQueryBuilder>.
+     *
+     * @return array
+     */
+    protected function getCollectedRecords()
+    {
+        $relatedRecords = [];
+
+        $queryBuilder = $this->getCollectedRecordsQueryBuilder();
+        $result = $queryBuilder->execute();
+
+        while ($record = $result->fetch()) {
+            $relatedRecords[] = $record;
+        }
+
+        return $relatedRecords;
+    }
+
+    /**
+     * Populates the content-entries of the storage
+     * Queries the underlying storage for entries of the collection
+     * and adds them to the collection data.
+     * 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.
+     */
+    public function loadContents()
+    {
+        $entries = $this->getCollectedRecords();
+        $this->removeAll();
+        foreach ($entries as $entry) {
+            $this->add($entry);
+        }
+    }
+
+    /**
+     * Returns an array of the persistable properties and contents
+     * which are processable by DataHandler.
+     * for internal usage in persist only.
+     *
+     * @return array
+     */
+    protected function getPersistableDataArray()
+    {
+        return [
+            'title' => $this->getTitle(),
+            'description' => $this->getDescription(),
+            'items' => $this->getItemUidList(true)
+        ];
+    }
+
+    /**
+     * Adds on entry to the collection
+     *
+     * @param mixed $data
+     */
+    public function add($data)
+    {
+        $this->storage->push($data);
+    }
+
+    /**
+     * Adds a set of entries to the collection
+     *
+     * @param CollectionInterface $other
+     */
+    public function addAll(CollectionInterface $other)
+    {
+        foreach ($other as $value) {
+            $this->add($value);
+        }
+    }
+
+    /**
+     * Removes the given entry from collection
+     * Note: not the given "index"
+     *
+     * @param mixed $data
+     */
+    public function remove($data)
+    {
+        $offset = 0;
+        foreach ($this->storage as $value) {
+            if ($value == $data) {
+                break;
+            }
+            $offset++;
+        }
+        $this->storage->offsetUnset($offset);
+    }
+
+    /**
+     * Removes all entries from the collection
+     * collection will be empty afterwards
+     */
+    public function removeAll()
+    {
+        $this->storage = new \SplDoublyLinkedList();
+    }
+
+    /**
+     * Gets the current available items.
+     *
+     * @return array
+     */
+    public function getItems()
+    {
+        $itemArray = [];
+        /** @var $item \TYPO3\CMS\Core\Resource\File */
+        foreach ($this->storage as $item) {
+            $itemArray[] = $item;
+        }
+        return $itemArray;
+    }
+
+    /**
+     * Sets the name of the language restrictions relation field
+     *
+     * @param string $field
+     */
+    public function setRelationFieldName($field)
+    {
+        $this->relationFieldName = $field;
+    }
+
+    /**
+     * Gets the name of the language restrictions relation field
+     *
+     * @return string
+     */
+    public function getRelationFieldName()
+    {
+        return $this->relationFieldName;
+    }
+
+    /**
+     * Getter for the storage table name
+     *
+     * @return string
+     */
+    public static function getStorageTableName()
+    {
+        return self::$storageTableName;
+    }
+
+    /**
+     * Getter for the storage items field
+     *
+     * @return string
+     */
+    public static function getStorageItemsField()
+    {
+        return self::$storageItemsField;
+    }
+}
diff --git a/Classes/LanguageRestriction/LanguageRestrictionRegistry.php b/Classes/LanguageRestriction/LanguageRestrictionRegistry.php
new file mode 100644 (file)
index 0000000..71d7972
--- /dev/null
@@ -0,0 +1,405 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: info
+ * Date: 25.10.2018
+ * Time: 11:34
+ */
+
+namespace Localizationteam\L10nmgr\LanguageRestriction;
+
+use Localizationteam\L10nmgr\Constants;
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\DebugUtility;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Lang\LanguageService;
+
+class LanguageRestrictionRegistry implements SingletonInterface
+{
+    /**
+     * @var array
+     */
+    protected $registry = [];
+
+    /**
+     * @var array
+     */
+    protected $extensions = [];
+
+    /**
+     * @var string
+     */
+    protected $template = '';
+
+    /**
+     * Creates this object.
+     */
+    public function __construct()
+    {
+        $this->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);
+    }
+
+    /**
+     * Returns a class instance
+     *
+     * @return object|LanguageRestrictionRegistry
+     */
+    public static function getInstance()
+    {
+        return GeneralUtility::makeInstance(__CLASS__);
+    }
+
+    /**
+     * Gets all language restrictable tables
+     *
+     * @return array
+     */
+    public function getLanguageRestrictableTables()
+    {
+        return array_keys($this->registry);
+    }
+
+    /**
+     * Apply TCA to all registered tables
+     *
+     * @internal
+     */
+    public function applyTcaForPreRegisteredTables()
+    {
+        $this->registerDefaultTranslationRestrictableTables();
+        foreach ($this->registry as $tableName => $fields) {
+            foreach ($fields as $fieldName => $_) {
+                $this->applyTcaForTableAndField($tableName, $fieldName);
+            }
+        }
+    }
+
+    /**
+     * Add default translation restrictable tables to the registry
+     */
+    protected function registerDefaultTranslationRestrictableTables()
+    {
+        $defaultTranslationRestrictableTables = GeneralUtility::trimExplode(
+            ',',
+            $GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultTranslationRestrictableTables'],
+            true
+        );
+        foreach ($defaultTranslationRestrictableTables as $defaultTranslationRestrictedTable) {
+            if (!$this->isRegistered($defaultTranslationRestrictedTable)) {
+                $this->add('core', $defaultTranslationRestrictedTable,
+                    Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME);
+            }
+        }
+    }
+
+    /**
+     * Adds a new language restriction configuration to this registry.
+     * TCA changes are directly applied
+     *
+     * @param string $extensionKey Extension key to be used
+     * @param string $tableName Name of the table to be registered
+     * @param string $fieldName Name of the field to be registered
+     * @param array $options Additional configuration options
+     *              + fieldList: field configuration to be added to showitems
+     *              + typesList: list of types that shall visualize the language restriction field
+     *              + position: insert position of the language restriction field
+     *              + label: backend label of the language restriction field
+     *              + fieldConfiguration: TCA field config array to override defaults
+     * @param bool $override If FALSE, any language restriction configuration for the same table / field is kept as is even though the new configuration is added
+     * @return bool
+     * @throws \InvalidArgumentException
+     * @throws \RuntimeException
+     */
+    public function add(
+        $extensionKey,
+        $tableName,
+        $fieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME,
+        array $options = [],
+        $override = true
+    ) {
+        $didRegister = false;
+        if (empty($tableName) || !is_string($tableName)) {
+            throw new \InvalidArgumentException('No or invalid table name "' . $tableName . '" given.', 1540460445);
+        }
+        if (empty($extensionKey) || !is_string($extensionKey)) {
+            throw new \InvalidArgumentException('No or invalid extension key "' . $extensionKey . '" given.',
+                1540460446);
+        }
+
+        if ($override) {
+            $this->remove($tableName, $fieldName);
+        }
+
+        if (!$this->isRegistered($tableName, $fieldName)) {
+            $this->registry[$tableName][$fieldName] = $options;
+            $this->extensions[$extensionKey][$tableName][$fieldName] = $fieldName;
+
+            if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
+                $this->applyTcaForTableAndField($tableName, $fieldName);
+                $didRegister = true;
+            }
+        }
+
+        return $didRegister;
+    }
+
+    /**
+     * Removes the given field in the given table from the registry if it is found.
+     *
+     * @param string $tableName The name of the table for which the registration should be removed.
+     * @param string $fieldName The name of the field for which the registration should be removed.
+     */
+    protected function remove($tableName, $fieldName)
+    {
+        if (!$this->isRegistered($tableName, $fieldName)) {
+            return;
+        }
+
+        unset($this->registry[$tableName][$fieldName]);
+
+        foreach ($this->extensions as $extensionKey => $tableFieldConfig) {
+            foreach ($tableFieldConfig as $extTableName => $fieldNameArray) {
+                if ($extTableName === $tableName && isset($fieldNameArray[$fieldName])) {
+                    unset($this->extensions[$extensionKey][$tableName][$fieldName]);
+                    break;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Tells whether a table has a language restriction configuration in the registry.
+     *
+     * @param string $tableName Name of the table to be looked up
+     * @param string $fieldName Name of the field to be looked up
+     * @return bool
+     */
+    public function isRegistered($tableName, $fieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME)
+    {
+        return isset($this->registry[$tableName][$fieldName]);
+    }
+
+    /**
+     * Applies the additions directly to the TCA
+     *
+     * @param string $tableName
+     * @param string $fieldName
+     */
+    protected function applyTcaForTableAndField($tableName, $fieldName)
+    {
+        $this->addTcaColumn($tableName, $fieldName, $this->registry[$tableName][$fieldName]);
+        $this->addToAllTCAtypes($tableName, $this->registry[$tableName][$fieldName]);
+    }
+
+    /**
+     * Add a new TCA Column
+     *
+     * @param string $tableName Name of the table to be language restrictable
+     * @param string $fieldName Name of the field to be used to store language restrictions
+     * @param array $options Additional configuration options
+     *              + fieldConfiguration: TCA field config array to override defaults
+     *              + label: backend label of the language restriction field
+     *              + interface: boolean if the language restriction should be included in the "interface" section of the TCA table
+     *              + l10n_mode
+     *              + l10n_display
+     */
+    protected function addTcaColumn($tableName, $fieldName, array $options)
+    {
+        // Makes sure to add more TCA to an existing structure
+        if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
+            // Take specific label into account
+            $label = 'LLL:EXT:l10nmgr/Resources/Private/Language/locallang_db.xlf:sys_language.restrictions';
+            if (!empty($options['label'])) {
+                $label = $options['label'];
+            }
+
+            // Take specific value of exclude flag into account
+            $exclude = true;
+            if (isset($options['exclude'])) {
+                $exclude = (bool)$options['exclude'];
+            }
+
+            $fieldConfiguration = empty($options['fieldConfiguration']) ? [] : $options['fieldConfiguration'];
+
+            $columns = [
+                $fieldName => [
+                    'exclude' => $exclude,
+                    'label'   => $label,
+                    'config'  => static::getTcaFieldConfiguration($tableName, $fieldName, $fieldConfiguration),
+                ],
+            ];
+
+            if (isset($options['l10n_mode'])) {
+                $columns[$fieldName]['l10n_mode'] = $options['l10n_mode'];
+            }
+            if (isset($options['l10n_display'])) {
+                $columns[$fieldName]['l10n_display'] = $options['l10n_display'];
+            }
+            if (isset($options['displayCond'])) {
+                $columns[$fieldName]['displayCond'] = $options['displayCond'];
+            }
+
+            // Add field to interface list per default (unless the 'interface' property is FALSE)
+            if (
+                (!isset($options['interface']) || $options['interface'])
+                && !empty($GLOBALS['TCA'][$tableName]['interface']['showRecordFieldList'])
+                && !GeneralUtility::inList($GLOBALS['TCA'][$tableName]['interface']['showRecordFieldList'], $fieldName)
+            ) {
+                $GLOBALS['TCA'][$tableName]['interface']['showRecordFieldList'] .= ',' . $fieldName;
+            }
+
+            // Adding fields to an existing table definition
+            ExtensionManagementUtility::addTCAcolumns($tableName, $columns);
+        }
+    }
+
+    /**
+     * Get the config array for given table and field.
+     * This method does NOT take care of adding sql fields, adding the field to TCA types
+     * nor does it set the MM_oppositeUsage in the sys_language TCA. This has to be taken care of manually!
+     *
+     * @param string $tableName The table name
+     * @param string $fieldName The field name (default l10nmgr_language_restriction)
+     * @param array $fieldConfigurationOverride Changes to the default configuration
+     * @return array
+     * @api
+     */
+    public static function getTcaFieldConfiguration(
+        $tableName,
+        $fieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME,
+        array $fieldConfigurationOverride = []
+    ) {
+        // 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,
+            'maxitems'            => 9999
+        ];
+
+        // Merge changes to TCA configuration
+        if (!empty($fieldConfigurationOverride)) {
+            ArrayUtility::mergeRecursiveWithOverrule(
+                $fieldConfiguration,
+                $fieldConfigurationOverride
+            );
+        }
+
+        return $fieldConfiguration;
+    }
+
+    /**
+     * Add a new field into the TCA types -> showitem
+     *
+     * @param string $tableName Name of the table to be language restrictable
+     * @param array $options Additional configuration options
+     *              + fieldList: field configuration to be added to showitems
+     *              + typesList: list of types that shall visualize the language restriction field
+     *              + position: insert position of the language restriction field
+     */
+    protected function addToAllTCAtypes($tableName, array $options)
+    {
+
+        // Makes sure to add more TCA to an existing structure
+        if (isset($GLOBALS['TCA'][$tableName]['columns'])) {
+            $fieldList = $options['fieldList'];
+
+            if (empty($fieldList)) {
+                $fieldList = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME;
+            }
+
+            $typesList = '';
+            if (isset($options['typesList']) && $options['typesList'] !== '') {
+                $typesList = $options['typesList'];
+            }
+
+            $position = $tableName === 'pages' ? 'after:l18n_cfg' : 'after:sys_language_uid';
+            if (!empty($options['position'])) {
+                $position = $options['position'];
+            }
+            DebugUtility::debug($fieldList);
+            // Makes the new "l10nmgr_language_restriction" field to be visible in TSFE.
+            ExtensionManagementUtility::addToAllTCAtypes($tableName, $fieldList, $typesList, $position);
+        }
+    }
+
+    /**
+     * A slot method to inject the required language restriction database fields to the
+     * tables definition string
+     *
+     * @param array $sqlString
+     * @return array
+     */
+    public function addLanguageRestrictionDatabaseSchemaToTablesDefinition(array $sqlString)
+    {
+        $this->registerDefaultTranslationRestrictableTables();
+        $sqlString[] = $this->getDatabaseTableDefinitions();
+        return ['sqlString' => $sqlString];
+    }
+
+    /**
+     * Generates tables definitions for all registered tables.
+     *
+     * @return string
+     */
+    public function getDatabaseTableDefinitions()
+    {
+        $sql = '';
+        foreach ($this->getExtensionKeys() as $extensionKey) {
+            $sql .= $this->getDatabaseTableDefinition($extensionKey);
+        }
+        return $sql;
+    }
+
+    /**
+     * Gets all extension keys that registered a language restriction configuration.
+     *
+     * @return array
+     */
+    public function getExtensionKeys()
+    {
+        return array_keys($this->extensions);
+    }
+
+    /**
+     * Generates table definitions for registered tables by an extension.
+     *
+     * @param string $extensionKey Extension key to have the database definitions created for
+     * @return string
+     */
+    public function getDatabaseTableDefinition($extensionKey)
+    {
+        if (!isset($this->extensions[$extensionKey]) || !is_array($this->extensions[$extensionKey])) {
+            return '';
+        }
+        $sql = '';
+
+        foreach ($this->extensions[$extensionKey] as $tableName => $fields) {
+            foreach ($fields as $fieldName) {
+                $sql .= sprintf($this->template, $tableName, $fieldName);
+            }
+        }
+        return $sql;
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+
+}
\ No newline at end of file
index ae3309f..50732e2 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+
 namespace Localizationteam\L10nmgr\Model;
 
 /***************************************************************
@@ -20,6 +21,7 @@ namespace Localizationteam\L10nmgr\Model;
  ***************************************************************/
 
 use Localizationteam\L10nmgr\Constants;
+use Localizationteam\L10nmgr\LanguageRestriction\Collection\LanguageRestrictionCollection;
 use Localizationteam\L10nmgr\Model\Tools\Tools;
 use TYPO3\CMS\Backend\Tree\View\PageTreeView;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -53,11 +55,11 @@ class L10nAccumulatedInformation
     /**
      * @var array Selected l10nmgr configuration
      */
-    var $l10ncfg = array();
+    var $l10ncfg = [];
     /**
      * @var array List of not allowed doktypes
      */
-    var $disallowDoktypes = array('--div--', '255');
+    var $disallowDoktypes = ['--div--', '255'];
     /**
      * @var int sys_language_uid of source language
      */
@@ -69,7 +71,7 @@ class L10nAccumulatedInformation
     /**
      * @var array Information about collected data for translation
      */
-    var $_accumulatedInformations = array();
+    var $_accumulatedInformations = [];
     /**
      * @var int Field count, might be needed by tranlation agencies
      */
@@ -81,15 +83,15 @@ class L10nAccumulatedInformation
     /**
      * @var array Extension's configuration as from the EM
      */
-    protected $extensionConfiguration = array();
+    protected $extensionConfiguration = [];
     /**
      * @var array Index of pages to be excluded from translation
      */
-    protected $excludeIndex = array();
+    protected $excludeIndex = [];
     /**
      * @var array Index of pages to be included with translation
      */
-    protected $includeIndex = array();
+    protected $includeIndex = [];
 
     /**
      * Constructor
@@ -147,7 +149,7 @@ class L10nAccumulatedInformation
         global $TCA;
         $tree = $this->tree;
         $l10ncfg = $this->l10ncfg;
-        $accum = array();
+        $accum = [];
         $sysLang = $this->sysLang;
         // FlexForm Diff data:
         $flexFormDiff = unserialize($l10ncfg['flexformdiff']);
@@ -169,7 +171,7 @@ class L10nAccumulatedInformation
                 $this->getBackendUser()->getTSConfigVal('options.additionalPreviewLanguages')));
         }
         if ($previewLanguage) {
-            $t8Tools->previewLanguages = array($previewLanguage);
+            $t8Tools->previewLanguages = [$previewLanguage];
         }
         $fileList = '';
         // Traverse tree elements:
@@ -196,13 +198,24 @@ class L10nAccumulatedInformation
             } elseif ($treeElement['row']['l10nmgr_configuration'] === Constants::L10NMGR_CONFIGURATION_EXCLUDE) {
                 $this->excludeIndex['pages:' . $pageId] = 1;
             }
+            if (!empty($treeElement['row'][Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME])) {
+                $languageIsRestricted = LanguageRestrictionCollection::load(
+                    (int)$previewLanguage,
+                    true,
+                    'pages',
+                    Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME
+                );
+                if (!empty($languageIsRestricted)) {
+                    $this->excludeIndex['pages:' . $pageId] = 1;
+                }
+            }
             if (!isset($this->excludeIndex['pages:' . $pageId]) && !in_array($treeElement['row']['doktype'],
                     $this->disallowDoktypes)
             ) {
                 $accum[$pageId]['header']['title'] = $treeElement['row']['title'];
                 $accum[$pageId]['header']['icon'] = $treeElement['HTML'];
                 $accum[$pageId]['header']['prevLang'] = $previewLanguage;
-                $accum[$pageId]['items'] = array();
+                $accum[$pageId]['items'] = [];
                 // Traverse tables:
                 foreach ($TCA as $table => $cfg) {
                     // Only those tables we want to work on:
@@ -218,6 +231,18 @@ class L10nAccumulatedInformation
                                 if (count($allRows)) {
                                     // Now, for each record, look for localization:
                                     foreach ($allRows as $row) {
+                                        if (!empty($row[Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME])) {
+                                            $languageIsRestricted = LanguageRestrictionCollection::load(
+                                                (int)$previewLanguage,
+                                                true,
+                                                $table,
+                                                Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME
+                                            );
+                                            if (!empty($languageIsRestricted)) {
+                                                $this->excludeIndex[$table . ':' . (int)$row['uid']] = 1;
+                                                continue;
+                                            }
+                                        }
                                         BackendUtility::workspaceOL($table, $row);
                                         if ($table === 'sys_file_reference') {
                                             $fileList .= $fileList ? ',' . (int)$row['uid_local'] : (int)$row['uid_local'];
@@ -273,10 +298,50 @@ class L10nAccumulatedInformation
     }
 
     /**
+     * Returns the Backend User
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * @param array $fieldsArray
+     */
+    protected function _increaseInternalCounters($fieldsArray)
+    {
+        if (is_array($fieldsArray)) {
+            $this->_fieldCount = $this->_fieldCount + count($fieldsArray);
+            if (function_exists('str_word_count')) {
+                foreach ($fieldsArray as $v) {
+                    $this->_wordCount = $this->_wordCount + str_word_count($v['defaultValue']);
+                }
+            }
+        }
+    }
+
+    /**
+     * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
+     *
+     * This method should be used instead of direct access to
+     * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
+     *
+     * @return DatabaseConnection
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
+     */
+    protected function getDatabaseConnection()
+    {
+        GeneralUtility::logDeprecatedFunction();
+        return $GLOBALS['TYPO3_DB'];
+    }
+
+    /**
      * @param string $indexList
      */
-    protected function addPagesMarkedAsIncluded($indexList, $excludeList) {
-        $this->includeIndex = array();
+    protected function addPagesMarkedAsIncluded($indexList, $excludeList)
+    {
+        $this->includeIndex = [];
         $this->excludeIndex = array_flip(GeneralUtility::trimExplode(',', $excludeList, true));
         if ($indexList) {
             $this->includeIndex = array_flip(GeneralUtility::trimExplode(',', $indexList, true));
@@ -308,11 +373,13 @@ class L10nAccumulatedInformation
      * @param int $uid
      * @param int $level
      */
-    protected function addSubPagesRecursively($uid, $level = 0) {
-        $level ++;
+    protected function addSubPagesRecursively($uid, $level = 0)
+    {
+        $level++;
         if ($uid > 0 && $level < 100) {
             $enableClause = BackendUtility::BEenableFields('pages');
-            $subPages = $this->getDatabaseConnection()->exec_SELECTgetRows('uid,pid,l10nmgr_configuration,l10nmgr_configuration_next_level', 'pages',
+            $subPages = $this->getDatabaseConnection()->exec_SELECTgetRows('uid,pid,l10nmgr_configuration,l10nmgr_configuration_next_level',
+                'pages',
                 'pid = ' . (int)$uid . $enableClause);
             if (!empty($subPages)) {
                 foreach ($subPages as $page) {
@@ -328,45 +395,6 @@ class L10nAccumulatedInformation
     }
 
     /**
-     * Returns the Backend User
-     * @return BackendUserAuthentication
-     */
-    protected function getBackendUser()
-    {
-        return $GLOBALS['BE_USER'];
-    }
-
-    /**
-     * @param array $fieldsArray
-     */
-    protected function _increaseInternalCounters($fieldsArray)
-    {
-        if (is_array($fieldsArray)) {
-            $this->_fieldCount = $this->_fieldCount + count($fieldsArray);
-            if (function_exists('str_word_count')) {
-                foreach ($fieldsArray as $v) {
-                    $this->_wordCount = $this->_wordCount + str_word_count($v['defaultValue']);
-                }
-            }
-        }
-    }
-
-    /**
-     * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
-     *
-     * This method should be used instead of direct access to
-     * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
-     *
-     * @return DatabaseConnection
-     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
-     */
-    protected function getDatabaseConnection()
-    {
-        GeneralUtility::logDeprecatedFunction();
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
      * @return int
      */
     public function getFieldCount()
diff --git a/Classes/Utility/L10nmgrExtensionManagementUtility.php b/Classes/Utility/L10nmgrExtensionManagementUtility.php
new file mode 100644 (file)
index 0000000..d5c6f78
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace Localizationteam\L10nmgr\Utility;
+
+/*
+ * 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!
+ */
+
+use Localizationteam\L10nmgr\Constants;
+use Localizationteam\L10nmgr\LanguageRestriction\LanguageRestrictionRegistry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * L10nmgr Extension Management functions
+ */
+class L10nmgrExtensionManagementUtility
+{
+
+    /**
+     * Makes translations of a table restrictable by adding value of restricted languages into the registry.
+     * FOR USE IN ext_localconf.php FILES or files in Configuration/TCA/Overrides/*.php Use the latter to benefit from TCA caching!
+     *
+     * @param string $extensionKey Extension key to be used
+     * @param string $tableName Name of the table to be categorized
+     * @param string $fieldName Name of the field to be used to store restricted languages
+     * @param array $options Additional configuration options
+     * @param bool $override If FALSE, any translation restriction configuration for the same table / field is kept as is even though the new configuration is added
+     * @see addTCAcolumns
+     * @see addToAllTCAtypes
+     */
+    public static function makeTranslationsRestrictable($extensionKey, $tableName, $fieldName = Constants::L10NMGR_LANGUAGE_RESTRICTION_FIELDNAME, array $options = [], $override = true)
+    {
+        // Update the category registry
+        $result = LanguageRestrictionRegistry::getInstance()->add($extensionKey, $tableName, $fieldName, $options, $override);
+        if ($result === false) {
+            $message = LanguageRestrictionRegistry::class . ': no category registered for table "%s". Key was already registered.';
+            /** @var $logger \TYPO3\CMS\Core\Log\Logger */
+            $logger = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Log\LogManager::class)->getLogger(__CLASS__);
+            $logger->warning(
+                sprintf($message, $tableName)
+            );
+        }
+    }
+}
index 39a1136..a32b779 100644 (file)
@@ -57,3 +57,8 @@
 
 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette('pages', 'l10nmgr_configuration', 'l10nmgr_configuration,l10nmgr_configuration_next_level');
 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('pages', '--palette--;LLL:EXT:l10nmgr/Resources/Private/Language/locallang_db.xlf:pages.palettes.l10nmgr_configuration;l10nmgr_configuration', '', 'after:l18n_cfg');
+
+\Localizationteam\L10nmgr\Utility\L10nmgrExtensionManagementUtility::makeTranslationsRestrictable(
+    'core',
+    'pages'
+);
diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php
new file mode 100644 (file)
index 0000000..6ab038e
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+\Localizationteam\L10nmgr\Utility\L10nmgrExtensionManagementUtility::makeTranslationsRestrictable(
+    'core',
+    'tt_content'
+);
index ac6ed29..6b25d6e 100644 (file)
                        <trans-unit id="tx_l10nmgr_priorities.element" xml:space="preserve">
                                <source>Element(s):</source>
                        </trans-unit>
-
                        <trans-unit id="pages.palettes.l10nmgr_configuration" xml:space="preserve">
                                <source>Additional Localization Manager Settings (might be overriden by l10nmgr configuration)</source>
                        </trans-unit>
                        <trans-unit id="pages.l10nmgr_configuration.I.3" xml:space="preserve">
                                <source>Always include within translations (overrides exclude)</source>
                        </trans-unit>
+                       <trans-unit id="sys_language.restrictions" xml:space="preserve">
+                               <source>Exclude this element from Localization Manager exports of selected languages</source>
+                       </trans-unit>
         </body>
     </file>
 </xliff>
\ No newline at end of file
index 6b30105..1916a1c 100644 (file)
@@ -35,3 +35,13 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['Localizationtea
 );
 
 $GLOBALS['TYPO3_CONF_VARS']['FE']['addRootLineFields'] .= ',l10nmgr_configuration,l10nmgr_configuration_next_level';
+
+$signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
+$signalSlotDispatcher->connect(
+    \TYPO3\CMS\Install\Service\SqlExpectedSchemaService::class,
+    'tablesDefinitionIsBeingBuilt',
+    \Localizationteam\L10nmgr\LanguageRestriction\LanguageRestrictionRegistry::class,
+    'addLanguageRestrictionDatabaseSchemaToTablesDefinition'
+);
+unset($signalSlotDispatcher);
+
index 5964746..7079877 100644 (file)
@@ -100,3 +100,18 @@ CREATE TABLE pages (
        l10nmgr_configuration tinyint(4) DEFAULT '0' NOT NULL,
        l10nmgr_configuration_next_level tinyint(4) DEFAULT '0' NOT NULL,
 );
+
+#
+# Table structure for table 'sys_language_l10nmgr_language_restricted_record_mm'
+#
+CREATE TABLE sys_language_l10nmgr_language_restricted_record_mm (
+       uid_local int(11) DEFAULT '0' NOT NULL,
+       uid_foreign int(11) DEFAULT '0' NOT NULL,
+       tablenames varchar(255) DEFAULT '' NOT NULL,
+       fieldname varchar(255) DEFAULT '' NOT NULL,
+       sorting int(11) DEFAULT '0' NOT NULL,
+       sorting_foreign int(11) DEFAULT '0' NOT NULL,
+
+       KEY uid_local_foreign (uid_local,uid_foreign),
+       KEY uid_foreign_tablefield (uid_foreign,tablenames(40),fieldname(3),sorting_foreign)
+);