[!!!][TASK] Remove TCA l10n_mode=mergeIfNotBlank 39/51239/14
authorOliver Hader <oliver@typo3.org>
Tue, 10 Jan 2017 13:41:26 +0000 (14:41 +0100)
committerBenni Mack <benni@typo3.org>
Wed, 11 Jan 2017 22:28:44 +0000 (23:28 +0100)
The TCA setting "l10n_mode=mergeIfNotBlank" for a single
column is removed from the list of values.

The functionality was there to use the value of a field
of the original record, if the value of the translated record
is empty (or trim'ed empty), and is then overlaid.

The new behaviour is to duplicate the behaviour during
the localize process, and then completely separate.

As a result the related TypoScript setting
config.sys_language_softMergeIfNotBlank is not required
anymore and is removed as well.

Resolves: #79243
Releases: master
Change-Id: I55f3ebd2fe2ddd8412101d5496a0da3c5ab64c68
Reviewed-on: https://review.typo3.org/51239
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
22 files changed:
typo3/sysext/backend/Classes/Form/Container/SingleFieldContainer.php
typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php
typo3/sysext/backend/Tests/Unit/Utility/BackendUtilityTest.php
typo3/sysext/core/Classes/DataHandling/DataHandler.php
typo3/sysext/core/Classes/Migrations/TcaMigration.php
typo3/sysext/core/Configuration/TCA/sys_category.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-79243-RemoveL10n_modeMergeIfNotBlank.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Breaking-79243-RemoveSys_language_softMergeIfNotBlank.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php
typo3/sysext/filemetadata/Configuration/TCA/Overrides/sys_file_metadata.php
typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Page/PageRepository.php
typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php
typo3/sysext/impexp/Tests/Functional/Fixtures/Extensions/impexp_group_files/Configuration/TCA/tx_impexpgroupfiles_item.php
typo3/sysext/install/Classes/Updates/DatabaseL10nModeUpdate.php [new file with mode: 0644]
typo3/sysext/install/ext_localconf.php
typo3/sysext/lang/Resources/Private/Language/locallang_misc.xlf
typo3/sysext/t3editor/Resources/Private/tsref.xml
typo3/sysext/t3editor/Resources/Public/JavaScript/parse_typoscript/tokenizetyposcript.js

index 6dbfa06..e77e906 100644 (file)
@@ -358,9 +358,8 @@ class SingleFieldContainer extends AbstractContainer
                 /** @var IconFactory $iconFactory */
                 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
                 if ($defaultLanguageValue !== '') {
-                    $item .= '<div class="t3-form-original-language" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:localizeMergeIfNotBlank')) . '">'
+                    $item .= '<div class="t3-form-original-language">'
                         . $iconFactory->getIcon($this->data['systemLanguageRows'][0]['flagIconIdentifier'], Icon::SIZE_SMALL)->render()
-                        . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode'])
                         . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>';
                 }
                 $additionalPreviewLanguages = $this->data['additionalLanguageRows'];
@@ -373,9 +372,8 @@ class SingleFieldContainer extends AbstractContainer
                         true
                     );
                     if ($defaultLanguageValue !== '') {
-                        $item .= '<div class="t3-form-original-language" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:localizeMergeIfNotBlank')) . '">'
+                        $item .= '<div class="t3-form-original-language">'
                             . $iconFactory->getIcon($this->data['systemLanguageRows'][$previewLanguage['sys_language_uid']]['flagIconIdentifier'], Icon::SIZE_SMALL)->render()
-                            . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode'])
                             . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>';
                     }
                 }
@@ -385,26 +383,6 @@ class SingleFieldContainer extends AbstractContainer
     }
 
     /**
-     * Renders an icon to indicate the way the translation and the original is merged (if this is relevant).
-     *
-     * If a field is defined as 'mergeIfNotBlank' this is useful information for an editor. He/she can leave the field blank and
-     * the original value will be used. Without this hint editors are likely to copy the contents even if it is not necessary.
-     *
-     * @param string $l10nMode Localization mode from TCA
-     * @return string
-     */
-    protected function getMergeBehaviourIcon($l10nMode)
-    {
-        $icon = '';
-        if ($l10nMode === 'mergeIfNotBlank') {
-            /** @var IconFactory $iconFactory */
-            $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-            $icon = $iconFactory->getIcon('actions-edit-merge-localization', Icon::SIZE_SMALL)->render();
-        }
-        return $icon;
-    }
-
-    /**
      * Renders the diff-view of default language record content compared with what the record was originally translated from.
      * Will render content if any is found in the internal array
      *
index f614d6f..90fdb5b 100644 (file)
@@ -108,7 +108,6 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface
                         // @todo: It might be necessary to fetch the value from default language record as well here,
                         // @todo: this was buggy in the "old" implementation and never worked. It was therefor left out here for now.
                         // @todo: To implement that, see if the foreign row is a localized overlay, fetch default and merge exclude
-                        // @todo: and mergeIfNotBlank if needed.
                         $recordTypeValue = $foreignRow[$foreignTableTypeField];
                     }
                 }
@@ -153,8 +152,7 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface
 
     /**
      * If a localized row is handled, the field value of the default language record
-     * is used instead if tca is configured as "exclude" or "mergeIfNotBlank" with
-     * empty localized value.
+     * is used instead if tca is configured as "exclude" with empty localized value.
      *
      * @param array $result Main "$result" data array
      * @param string $field Field name to fetch value for
@@ -169,16 +167,8 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface
             && $result['databaseRow'][$result['processedTca']['ctrl']['languageField']] > 0
             // l10n_mode for field is configured
             && !empty($result['processedTca']['columns'][$field]['l10n_mode'])
-            && (
-                // is exclude -> fall back to value of default record
-                $result['processedTca']['columns'][$field]['l10n_mode'] === 'exclude'
-                // is mergeIfNotBlank and own value is empty -> fall back to value of default record
-                || (
-                    $result['processedTca']['columns'][$field]['l10n_mode'] === 'mergeIfNotBlank'
-                    // 0 means "not empty"
-                    && $result['databaseRow'][$field] === ''
-                )
-            )
+            // is exclude -> fall back to value of default record
+            && $result['processedTca']['columns'][$field]['l10n_mode'] === 'exclude'
         ) {
             $value = $result['defaultLanguageRow'][$field];
         }
index e7b4dc2..c2a3041 100644 (file)
@@ -2093,7 +2093,7 @@ class BackendUtility
             $l10n_mode = isset($GLOBALS['TCA'][$originalTable]['columns'][$field]['l10n_mode'])
                 ? $GLOBALS['TCA'][$originalTable]['columns'][$field]['l10n_mode']
                 : '';
-            if ($l10n_mode === 'exclude' || ($l10n_mode === 'mergeIfNotBlank' && trim($row[$field]) === '')) {
+            if ($l10n_mode === 'exclude') {
                 $row[$field] = $originalRow[$field];
             }
         }
index b680d76..ae42bec 100644 (file)
@@ -298,78 +298,6 @@ class DatabaseRecordTypeValueTest extends \TYPO3\CMS\Components\TestingFramework
     /**
      * @test
      */
-    public function addDataSetsRecordTypeValueToValueOfDefaultLanguageRecordIfConfiguredAsMergeIfNotBlank()
-    {
-        $input = [
-            'recordTypeValue' => '',
-            'processedTca' => [
-                'ctrl' => [
-                    'languageField' => 'sys_language_uid',
-                    'type' => 'aField',
-                ],
-                'columns' => [
-                    'aField' => [
-                        'l10n_mode' => 'mergeIfNotBlank',
-                    ],
-                ],
-                'types' => [
-                    '3' => 'foo',
-                ],
-            ],
-            'databaseRow' => [
-                'sys_language_uid' => 2,
-                'aField' => '',
-            ],
-            'defaultLanguageRow' => [
-                'aField' => 3,
-            ],
-        ];
-
-        $expected = $input;
-        $expected['recordTypeValue'] = '3';
-
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
-    /**
-     * @test
-     */
-    public function addDataSetsRecordTypeValueToValueOfLocalizedRecordIfConfiguredAsMergeIfNotBlankButNotBlank()
-    {
-        $input = [
-            'recordTypeValue' => '',
-            'processedTca' => [
-                'ctrl' => [
-                    'languageField' => 'sys_language_uid',
-                    'type' => 'aField',
-                ],
-                'columns' => [
-                    'aField' => [
-                        'l10n_mode' => 'mergeIfNotBlank',
-                    ],
-                ],
-                'types' => [
-                    '3' => 'foo',
-                ],
-            ],
-            'databaseRow' => [
-                'sys_language_uid' => 2,
-                'aField' => 3,
-            ],
-            'defaultLanguageRow' => [
-                'aField' => 4,
-            ],
-        ];
-
-        $expected = $input;
-        $expected['recordTypeValue'] = '3';
-
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
-    /**
-     * @test
-     */
     public function addDataThrowsExceptionForForeignTypeConfigurationNotAsSelectOrGroup()
     {
         $input = [
index 097558f..0fa125f 100644 (file)
@@ -913,35 +913,6 @@ class BackendUtilityTest extends \TYPO3\CMS\Components\TestingFramework\Core\Uni
     public function replaceL10nModeFieldsReplacesFieldsDataProvider()
     {
         return [
-            'same table: mergeIfNotBlank' => [
-                'foo',
-                [
-                    'origUid' => 1,
-                    'field2' => 'fdas',
-                    'field3' => 'trans',
-                ],
-                [
-                    'foo' => [
-                        'ctrl' => [
-                            'transOrigPointerField' => 'origUid'
-                        ],
-                        'columns' => [
-                            'field2' => ['l10n_mode' => 'mergeIfNotBlank'],
-                            'field3' => ['l10n_mode' => 'mergeIfNotBlank']
-                        ]
-                    ]
-                ],
-                [
-                    'origUid' => 0,
-                    'field2' => 'basic',
-                    'field3' => '',
-                ],
-                [
-                    'origUid' => 1,
-                    'field2' => 'fdas',
-                    'field3' => 'trans',
-                ]
-            ],
             'same table: exclude' => [
                 'foo',
                 [
index ef201c4..3137bfd 100644 (file)
@@ -4623,7 +4623,7 @@ class DataHandler
                     $overrideValues[$fN] = '[' . $translateToMsg . '] ' . $row[$fN];
                 }
             } elseif (
-                ($fCfg['l10n_mode'] === 'exclude' || $fCfg['l10n_mode'] === 'mergeIfNotBlank')
+                ($fCfg['l10n_mode'] === 'exclude')
                     && $fN != $GLOBALS['TCA'][$Ttable]['ctrl']['languageField']
                     && $fN != $GLOBALS['TCA'][$Ttable]['ctrl']['transOrigPointerField']
              ) {
index ddf0232..7307c3f 100644 (file)
@@ -950,6 +950,11 @@ class TcaMigration
                     $this->messages[] = 'The TCA setting \'noCopy\' was removed '
                         . 'in TCA ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'l10n_mode\']';
                 }
+                if ($fieldConfig['l10n_mode'] === 'mergeIfNotBlank') {
+                    unset($fieldConfig['l10n_mode']);
+                    $this->messages[] = 'The TCA setting \'mergeIfNotBlank\' was removed '
+                        . 'in TCA ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'l10n_mode\']';
+                }
             }
         }
         return $tca;
index f741b96..9e4dd35 100644 (file)
@@ -107,7 +107,6 @@ return [
         ],
         'starttime' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
             'config' => [
                 'type' => 'input',
@@ -118,7 +117,6 @@ return [
         ],
         'endtime' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
             'config' => [
                 'type' => 'input',
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-79243-RemoveL10n_modeMergeIfNotBlank.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-79243-RemoveL10n_modeMergeIfNotBlank.rst
new file mode 100644 (file)
index 0000000..6fa374d
--- /dev/null
@@ -0,0 +1,55 @@
+.. include:: ../../Includes.txt
+
+===================================================
+Breaking: #79243 - Remove l10n_mode mergeIfNotBlank
+===================================================
+
+See :issue:`79243`
+
+Description
+===========
+
+The setting `mergeIfNotBlank` is removed from the list of possible values of
+the TCA column property `l10n_mode` without any replacement.
+
+
+Impact
+======
+
+Previously values of a localization having a dependent parent record were taken
+from the parent record if `l10n_mode` for the particular field was set to
+`mergeIfNotBlank` and the value in the localization was empty. Now, this value
+is duplicated once during the creation of the localized record and has to be
+modified manually if required.
+
+
+Affected Installations
+======================
+
+All instances with extensions setting TCA options and having
+`$GLOBALS['TCA'][<table-name>]['columns'][<column-name>]['l10n_mode']` set to `mergeIfNotBlank`.
+
+
+Migration
+=========
+
+First execute the upgrade wizard
+**Migrate values in database records having "l10n_mode" set** in the install tool.
+After that, remove `$GLOBALS['TCA'][<table-name>]['columns'][<column-name>]['l10n_mode']`
+if it is set to `mergeIfNotBlank`. If `l10n_mode` is removed before the upgrade wizard
+has been executed, nothing will be migrated - thus, it's important to keep that order
+of migration.
+
+The upgrade wizard executes the following field usages:
+
+* inline children, pointing to `sys_file_reference`:
+  file references are localized for the the localization, if missing there
+* group fields, basically not using MM intermediate tables:
+  value is cloned to the accordant field in the localization, if empty there
+* any other field type:
+  value is cloned to the accordant field in the localization, is blank there
+
+The term `blank` refers to an empty string (`''`), `empty` refers to an empty
+string, null values and zero values (numeric and string).
+
+.. index:: Database, TCA
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-79243-RemoveSys_language_softMergeIfNotBlank.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-79243-RemoveSys_language_softMergeIfNotBlank.rst
new file mode 100644 (file)
index 0000000..dc991a0
--- /dev/null
@@ -0,0 +1,22 @@
+.. include:: ../../Includes.txt
+
+==========================================================
+Breaking: #79243 - Remove sys_language_softMergeIfNotBlank
+==========================================================
+
+See :issue:`79243`
+
+Description
+===========
+
+The TypoScript setting `config.sys_language_softMergeIfNotBlank` is removed
+without any replacement. This is a result of removing the TCA setting
+`mergeIfNotBlank` is removed from the list of possible values for `l10n_mode`.
+
+
+Migration
+=========
+
+Remove TypoScript setting `config.sys_language_softMergeIfNotBlank`.
+
+.. index:: Frontend, TypoScript
\ No newline at end of file
index 756aead..c7411f8 100644 (file)
@@ -1989,6 +1989,25 @@ class TcaMigrationTest extends \TYPO3\CMS\Components\TestingFramework\Core\UnitT
                     ],
                 ]
             ],
+            'remove l10n_mode mergeIfNotBlank' => [
+                [
+                    'aTable' => [
+                        'columns' => [
+                            'aColumn' => [
+                                'l10n_mode' => 'mergeIfNotBlank',
+                            ],
+                        ],
+                    ],
+                ],
+                [
+                    'aTable' => [
+                        'columns' => [
+                            'aColumn' => [
+                            ],
+                        ],
+                    ],
+                ]
+            ],
         ];
     }
 
index 8fa1b3c..3e588ec 100644 (file)
@@ -270,7 +270,6 @@ $tca = [
         ],
         'location_country' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'l10n_display' => '',
             'label' => 'LLL:EXT:filemetadata/Resources/Private/Language/locallang_tca.xlf:sys_file_metadata.location_country',
             'config' => [
@@ -281,7 +280,6 @@ $tca = [
         ],
         'location_region' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'l10n_display' => '',
             'label' => 'LLL:EXT:filemetadata/Resources/Private/Language/locallang_tca.xlf:sys_file_metadata.location_region',
             'config' => [
@@ -292,7 +290,6 @@ $tca = [
         ],
         'location_city' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'l10n_display' => '',
             'label' => 'LLL:EXT:filemetadata/Resources/Private/Language/locallang_tca.xlf:sys_file_metadata.location_city',
             'config' => [
index d564ea9..e186d60 100644 (file)
@@ -1731,8 +1731,6 @@ abstract class AbstractMenuContentObject
      *
      * Since the pages records used for menu rendering are overlaid by default,
      * the original 'shortcut' value is lost, if a translation did not define one.
-     * The behaviour in TSFE can be compared to the 'mergeIfNotBlank' feature, but
-     * it's hardcoded there and not related to the mentioned setting at all.
      *
      * @param array $page
      * @return array
index c75fdf9..baf23f8 100644 (file)
@@ -2730,12 +2730,6 @@ class TypoScriptFrontendController
             }
         }
 
-        // Setting softMergeIfNotBlank:
-        $table_fields = GeneralUtility::trimExplode(',', $this->config['config']['sys_language_softMergeIfNotBlank'], true);
-        foreach ($table_fields as $TF) {
-            list($tN, $fN) = explode(':', $TF);
-            $GLOBALS['TCA'][$tN]['columns'][$fN]['l10n_mode'] = 'mergeIfNotBlank';
-        }
         // Setting softExclude:
         $table_fields = GeneralUtility::trimExplode(',', $this->config['config']['sys_language_softExclude'], true);
         foreach ($table_fields as $TF) {
index 2f61e97..1ff9ef5 100644 (file)
@@ -1933,17 +1933,6 @@ class PageRepository
 
         if ($l10n_mode === 'exclude') {
             $shouldFieldBeOverlaid = false;
-        } elseif ($l10n_mode === 'mergeIfNotBlank') {
-            $checkValue = $value;
-
-            // 0 values are considered blank when coming from a group field
-            if (empty($value) && $GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'group') {
-                $checkValue = '';
-            }
-
-            if ($checkValue === [] || !is_array($checkValue) && trim($checkValue) === '') {
-                $shouldFieldBeOverlaid = false;
-            }
         }
 
         return $shouldFieldBeOverlaid;
index 3a73a2e..6449b18 100644 (file)
@@ -104,14 +104,6 @@ class PageRepositoryTest extends \TYPO3\CMS\Components\TestingFramework\Core\Uni
                 'l10n_mode' => 'exclude',
                 'config' => ['type' => 'input'],
             ],
-            'mergeIfNotBlank' => [
-                'l10n_mode' => 'mergeIfNotBlank',
-                'config' => ['type' => 'input'],
-            ],
-            'mergeIfNotBlank_group' => [
-                'l10n_mode' => 'mergeIfNotBlank',
-                'config' => ['type' => 'group'],
-            ],
             'default' => [
                 // no l10n_mode set
                 'config' => ['type' => 'input'],
@@ -140,16 +132,6 @@ class PageRepositoryTest extends \TYPO3\CMS\Components\TestingFramework\Core\Uni
             ['exclude',               'fake_table', '',       false, 'exclude field with empty string'],
             ['exclude',               'fake_table', 'foobar', false, 'exclude field with non-empty string'],
 
-            ['mergeIfNotBlank',       'fake_table', '',       false, 'mergeIfNotBlank is not merged with empty string'],
-            ['mergeIfNotBlank',       'fake_table', 0,        true,  'mergeIfNotBlank is merged with 0'],
-            ['mergeIfNotBlank',       'fake_table', '0',      true,  'mergeIfNotBlank is merged with "0"'],
-            ['mergeIfNotBlank',       'fake_table', 'foobar', true,  'mergeIfNotBlank is merged with non-empty string'],
-
-            ['mergeIfNotBlank_group', 'fake_table', '',       false, 'mergeIfNotBlank on group is not merged empty string'],
-            ['mergeIfNotBlank_group', 'fake_table', 0,        false, 'mergeIfNotBlank on group is not merged with 0'],
-            ['mergeIfNotBlank_group', 'fake_table', '0',      false, 'mergeIfNotBlank on group is not merged with "0"'],
-            ['mergeIfNotBlank_group', 'fake_table', 'foobar', true,  'mergeIfNotBlank on group is merged with non-empty string'],
-
             ['prefixLangTitle',       'fake_table', 'foobar', true,  'prefixLangTitle is merged with non-empty string'],
             ['prefixLangTitle',       'fake_table', '',       true,  'prefixLangTitle is merged with empty string'],
         ];
index f28c83f..39d449c 100644 (file)
@@ -52,7 +52,6 @@ return [
         ],
         'starttime' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
             'config' => [
                 'type' => 'input',
@@ -66,7 +65,6 @@ return [
         ],
         'endtime' => [
             'exclude' => true,
-            'l10n_mode' => 'mergeIfNotBlank',
             'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
             'config' => [
                 'type' => 'input',
diff --git a/typo3/sysext/install/Classes/Updates/DatabaseL10nModeUpdate.php b/typo3/sysext/install/Classes/Updates/DatabaseL10nModeUpdate.php
new file mode 100644 (file)
index 0000000..12bffa2
--- /dev/null
@@ -0,0 +1,374 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/*
+ * 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 TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Service\LoadTcaService;
+
+/**
+ * Migrate values for database records having columns
+ * using "l10n_mode" set to "mergeIfNotBlank".
+ */
+class DatabaseL10nModeUpdate extends AbstractUpdate
+{
+    /**
+     * @var string
+     */
+    protected $title = 'Migrate values in database records having "l10n_mode" set';
+
+    /**
+     * Field names that previously had a migrated l10n_mode setting in TCA.
+     *
+     * @var array
+     */
+    protected $migratedL10nCoreFieldNames = [
+        'sys_category' => [
+            'starttime' => 'mergeIfNotBlank',
+            'endtime' => 'mergeIfNotBlank',
+        ],
+        'sys_file_metadata' => [
+            'location_country' => 'mergeIfNotBlank',
+            'location_region' => 'mergeIfNotBlank',
+            'location_city' => 'mergeIfNotBlank',
+        ],
+    ];
+
+    /**
+     * Checks if an update is needed
+     *
+     * @param string &$description The description for the update
+     * @return bool Whether an update is needed (TRUE) or not (FALSE)
+     * @throws \InvalidArgumentException
+     * @throws \Doctrine\DBAL\DBALException
+     */
+    public function checkForUpdate(&$description)
+    {
+        if ($this->isWizardDone()) {
+            return false;
+        }
+        if (count($this->getL10nModePayload()) === 0) {
+            $this->markWizardAsDone();
+            return false;
+        }
+
+        $description = 'Clones values for database records having columns using'
+            . ' "l10n_mode" set to "mergeIfNotBlank".';
+        return true;
+    }
+
+    /**
+     * Performs the accordant updates.
+     *
+     * @param array &$databaseQueries Queries done in this update
+     * @param mixed &$customMessages Custom messages
+     * @return bool
+     */
+    public function performUpdate(array &$databaseQueries, &$customMessages)
+    {
+        $payload = $this->getL10nModePayload();
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $fakeAdminUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
+        $fakeAdminUser->user = ['admin' => 1];
+
+        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'])) {
+            $dataHandlerHooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'];
+            unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']);
+        }
+
+        foreach ($payload as $tableName => $tablePayload) {
+            $fields = $tablePayload['fields'];
+            $fieldNames = array_keys($fields);
+            $fieldTypes = $tablePayload['fieldTypes'];
+            $sourceFieldName = $tablePayload['sourceFieldName'];
+
+            foreach ($tablePayload['sources'] as $source => $ids) {
+                $sourceTableName = $tableName;
+                if ($tableName === 'pages_language_overlay') {
+                    $sourceTableName = 'pages';
+                }
+                $sourceRow = $this->getRow($sourceTableName, $source);
+
+                foreach ($ids as $id) {
+                    $updateValues = [];
+
+                    $row = $this->getRow($tableName, $id);
+                    foreach ($row as $fieldName => $fieldValue) {
+                        if (!in_array($fieldName, $fieldNames)) {
+                            continue;
+                        }
+
+                        if (
+                            // default
+                            empty($fieldTypes[$fieldName])
+                            && trim((string)$fieldValue) === ''
+                            // group types (basically as comma seprated values)
+                            || $fieldTypes[$fieldName] === 'group'
+                            && (
+                                $fieldValue === ''
+                                || $fieldValue === null
+                                || (string)$fieldValue === '0'
+                            )
+                        ) {
+                            $updateValues[$fieldName] = $sourceRow[$fieldName];
+                        }
+                        // inline types, but only file references
+                        if (
+                            !empty($fieldTypes[$fieldName])
+                            && $fieldTypes[$fieldName] === 'inline/FAL'
+                        ) {
+                            $parentId = (!empty($row['t3ver_oid']) ? $row['t3ver_oid'] : $source);
+                            $commandMap = [
+                                $sourceTableName => [
+                                    $parentId => [
+                                        'inlineLocalizeSynchronize' => [
+                                            'action' => 'localize',
+                                            'language' => $row[$sourceFieldName],
+                                            'field' => $fieldName,
+                                        ]
+                                    ]
+                                ]
+                            ];
+                            $fakeAdminUser->workspace = $row['t3ver_wsid'];
+                            $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
+                            $dataHandler->start([], $commandMap, $fakeAdminUser);
+                            $dataHandler->process_cmdmap();
+                        }
+                    }
+
+                    if (empty($updateValues)) {
+                        continue;
+                    }
+
+                    $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
+                    foreach ($updateValues as $updateFieldName => $updateValue) {
+                        $queryBuilder->set($updateFieldName, $updateValue);
+                    }
+
+                    $queryBuilder
+                        ->update($tableName)
+                        ->where(
+                            $queryBuilder->expr()->eq(
+                                'uid',
+                                $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                            )
+                        )
+                        ->execute();
+                    $databaseQueries[] = $queryBuilder->getSQL();
+                }
+            }
+        }
+
+        if (!empty($dataHandlerHooks)) {
+            $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'] = $dataHandlerHooks;
+        }
+
+        $this->markWizardAsDone();
+        return true;
+    }
+
+    /**
+     * Retrieves field names grouped per table name having "l10n_mode" set
+     * to a relevant value that shall be migrated in database records.
+     *
+     * Resulting array is structured like this:
+     * + table name
+     *   + fields: [field a, field b, ...]
+     *   + sources
+     *     + source uid: [localization uid, localization uid, ...]
+     *
+     * @return array
+     */
+    protected function getL10nModePayload(): array
+    {
+        $payload = [];
+
+        $loadTcaService = GeneralUtility::makeInstance(LoadTcaService::class);
+        $loadTcaService->loadExtensionTablesWithoutMigration();
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+
+        foreach ($GLOBALS['TCA'] as $tableName => $tableDefinition) {
+            if (
+                empty($tableDefinition['columns'])
+                || !is_array($tableDefinition['columns'])
+                || empty($tableDefinition['ctrl']['languageField'])
+                || empty($tableDefinition['ctrl']['transOrigPointerField'])
+            ) {
+                continue;
+            }
+
+            $fields = [];
+            $fieldTypes = [];
+            foreach ($tableDefinition['columns'] as $fieldName => $fieldConfiguration) {
+                if (
+                    empty($fieldConfiguration['l10n_mode'])
+                    && !empty($this->migratedL10nCoreFieldNames[$tableName][$fieldName])
+                ) {
+                    $fieldConfiguration['l10n_mode'] = $this->migratedL10nCoreFieldNames[$tableName][$fieldName];
+                }
+
+                if (
+                    empty($fieldConfiguration['l10n_mode'])
+                    || empty($fieldConfiguration['config']['type'])
+                ) {
+                    continue;
+                }
+                if ($fieldConfiguration['l10n_mode'] === 'mergeIfNotBlank') {
+                    $fields[$fieldName] = $fieldConfiguration;
+                }
+            }
+
+            if (empty($fields)) {
+                continue;
+            }
+
+            $parentQueryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
+            $parentQueryBuilder->getRestrictions()->removeAll();
+            $parentQueryBuilder->from($tableName);
+
+            $predicates = [];
+            foreach ($fields as $fieldName => $fieldConfiguration) {
+                $predicates[] = $parentQueryBuilder->expr()->comparison(
+                    $parentQueryBuilder->expr()->trim($fieldName),
+                    ExpressionBuilder::EQ,
+                    $parentQueryBuilder->createNamedParameter('', \PDO::PARAM_STR)
+                );
+                $predicates[] = $parentQueryBuilder->expr()->eq(
+                    $fieldName,
+                    $parentQueryBuilder->createNamedParameter('', \PDO::PARAM_STR)
+                );
+
+                if (empty($fieldConfiguration['config']['type'])) {
+                    continue;
+                }
+
+                if ($fieldConfiguration['config']['type'] === 'group') {
+                    $fieldTypes[$fieldName] = 'group';
+                    $predicates[] = $parentQueryBuilder->expr()->isNull(
+                        $fieldName
+                    );
+                    $predicates[] = $parentQueryBuilder->expr()->eq(
+                        $fieldName,
+                        $parentQueryBuilder->createNamedParameter('0', \PDO::PARAM_STR)
+                    );
+                }
+                if (
+                    $fieldConfiguration['config']['type'] === 'inline'
+                    && !empty($fieldConfiguration['config']['foreign_field'])
+                    && $fieldConfiguration['config']['foreign_field'] === 'uid_foreign'
+                    && !empty($fieldConfiguration['config']['foreign_table'])
+                    && $fieldConfiguration['config']['foreign_table'] === 'sys_file_reference'
+                ) {
+                    $fieldTypes[$fieldName] = 'inline/FAL';
+
+                    $childQueryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
+                    $childQueryBuilder->getRestrictions()->removeAll();
+                    $childExpression = $childQueryBuilder
+                        ->count('uid')
+                        ->from('sys_file_reference')
+                        ->andWhere(
+                            $childQueryBuilder->expr()->eq(
+                                'sys_file_reference.uid_foreign',
+                                $parentQueryBuilder->getConnection()->quoteIdentifier($tableName . '.uid')
+                            ),
+                            $childQueryBuilder->expr()->eq(
+                                'sys_file_reference.tablenames',
+                                $parentQueryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
+                            ),
+                            $childQueryBuilder->expr()->eq(
+                                'sys_file_reference.fieldname',
+                                $parentQueryBuilder->createNamedParameter($fieldName, \PDO::PARAM_STR)
+                            )
+                        );
+
+                    $predicates[] = $parentQueryBuilder->expr()->comparison(
+                        '(' . $childExpression->getSQL() . ')',
+                        ExpressionBuilder::GT,
+                        $parentQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    );
+                }
+            }
+
+            $sourceFieldName = $tableDefinition['ctrl']['transOrigPointerField'];
+            $selectFieldNames = ['uid', $sourceFieldName];
+
+            if (!empty($tableDefinition['ctrl']['versioningWS'])) {
+                $selectFieldNames = array_merge(
+                    $selectFieldNames,
+                    ['t3ver_wsid', 't3ver_oid']
+                );
+            }
+
+            $statement = $parentQueryBuilder
+                ->select(...$selectFieldNames)
+                ->andWhere(
+                    $parentQueryBuilder->expr()->gt(
+                        $tableDefinition['ctrl']['languageField'],
+                        $parentQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    ),
+                    $parentQueryBuilder->expr()->gt(
+                        $sourceFieldName,
+                        $parentQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    ),
+                    $parentQueryBuilder->expr()->orX(...$predicates)
+                )
+                ->execute();
+
+            foreach ($statement as $row) {
+                $source = $row[$sourceFieldName];
+                $payload[$tableName]['sources'][$source][] = $row['uid'];
+            }
+
+            if (
+                empty($payload[$tableName]['fields'])
+                && !empty($payload[$tableName]['sources'])
+            ) {
+                $payload[$tableName]['fields'] = $fields;
+                $payload[$tableName]['fieldTypes'] = $fieldTypes;
+                $payload[$tableName]['sourceFieldName'] = $sourceFieldName;
+            }
+        }
+
+        return $payload;
+    }
+
+    /**
+     * @param string $tableName
+     * @param int $id
+     * @return array
+     */
+    protected function getRow(string $tableName, int $id)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $statement = $queryBuilder
+            ->select('*')
+            ->from($tableName)
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'uid',
+                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                )
+            )
+            ->execute();
+
+        return $statement->fetch();
+    }
+}
index 4a7a0dc..1e9ec86 100644 (file)
@@ -49,3 +49,5 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\TYPO3\CMS\In
     = \TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\TYPO3\CMS\Install\Updates\CommandLineBackendUserRemovalUpdate::class]
     = \TYPO3\CMS\Install\Updates\CommandLineBackendUserRemovalUpdate::class;
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\TYPO3\CMS\Install\Updates\DatabaseL10nModeUpdate::class]
+    = \TYPO3\CMS\Install\Updates\DatabaseL10nModeUpdate::class;
index be08fa0..f43fc20 100644 (file)
                        <trans-unit id="localizeAllRecords">
                                <source>Localize all records</source>
                        </trans-unit>
-                       <trans-unit id="localizeMergeIfNotBlank">
-                               <source>The value of the default language will be used if this field is left empty in the translation.</source>
-                       </trans-unit>
                        <trans-unit id="synchronizeWithOriginalLanguage">
                                <source>Synchronize with original language</source>
                        </trans-unit>
index 2551495..99d2d88 100644 (file)
@@ -852,26 +852,14 @@ hideNonTranslated : If this keyword is used a record that has no translation wil
 ]]></default>
                </property>
                <property name="sys_language_softExclude" type="string">
-                       <description><![CDATA[Setting additional "exclude" flags for l10n_mode in TCA for frontend rendering. Works exactly like sys_language_softMergeIfNotBlank (see that for details - same Syntax!).
-
-Fields set in this property will override if the same field is set for "sys_language_softMergeIfNotBlank".]]></description>
-                       <default><![CDATA[
-]]></default>
-               </property>
-               <property name="sys_language_softMergeIfNotBlank" type="string">
-                       <description><![CDATA[Setting additional "mergeIfNotBlank" fields from TypoScript.
-
-Background:
-In TCA you can configure "l10n_mode" - localization mode - for each field. Two of the options affect how the frontend displays content; The values "exclude" and "mergeIfNotBlank" (see "TYPO3 Core API" document for details). The first ("exclude") simply means that the field when found in a translation of a record will not be overlaid the default records field value. The second ("mergeIfNotBlank") means that it will be overlaid only if it has a non-blank value.
-Since it might be practical to set up fields for "mergeIfNotBlank" on a per-site basis this options allows you to override additional fields from tables.
+                       <description><![CDATA[Setting additional "exclude" flags for l10n_mode in TCA for frontend rendering.
 
 Syntax:
  [table]:[field],  [table]:[field],  [table]:[field], ...
 
 Example:
-config.sys_language_softMergeIfNotBlank = tt_content:image , tt_content:header
-
-This setting means that the header and image field of content elements will be used from the translation only if they had a non-blank value. For the image field this might be very practical because it means that the image(s) from the default translation will be used unless other images are inserted!]]></description>
+config.sys_language_softExclude = tt_content:image , tt_content:header
+]]></description>
                        <default><![CDATA[
 ]]></default>
                </property>
index a2ef49c..ac5f13e 100644 (file)
@@ -861,7 +861,6 @@ var typoscriptWords = {
        'sys_filemounts': 'keyword3',
        'sys_language_mode': 'reserved',
        'sys_language_overlay': 'reserved',
-       'sys_language_softMergeIfNotBlank': 'reserved',
        'sys_language_uid': 'reserved',
        'sys_note': 'keyword3',
        'sys_template': 'keyword3',