From ba9b75f625f257910104219038ab30a0fb032a87 Mon Sep 17 00:00:00 2001 From: catch <6915-catch@users.noreply.drupalcode.org> Date: Mon, 3 Feb 2025 12:41:48 +0000 Subject: [PATCH] Issue #3258581 by godotislate, nikolay shapovalov, boromino, quietone, merlin06, benjifisher: Move I18nQueryTrait from content_translation to migrate_drupal --- .../source/d7/BlockCustomTranslation.php | 2 +- .../Plugin/migrate/source/I18nQueryTrait.php | 7 ++ .../migrate/source/d6/MenuLinkTranslation.php | 4 +- .../migrate/source/d7/MenuLinkTranslation.php | 4 +- .../Plugin/migrate/source/I18nQueryTrait.php | 86 ++++++++++++++++++ .../tests/src/Kernel/I18nQueryTraitTest.php | 91 +++++++++++++++++++ .../source/d7/TermLocalizedTranslation.php | 2 +- 7 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 core/modules/migrate_drupal/src/Plugin/migrate/source/I18nQueryTrait.php create mode 100644 core/modules/migrate_drupal/tests/src/Kernel/I18nQueryTraitTest.php diff --git a/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php b/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php index ce592d27fb34..346b280ca08e 100644 --- a/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php +++ b/core/modules/block_content/src/Plugin/migrate/source/d7/BlockCustomTranslation.php @@ -4,7 +4,7 @@ use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase; -use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait; +use Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait; /** * Drupal 7 i18n content block translations source from database. diff --git a/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php b/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php index 29a64920b721..f6ea52e84aba 100644 --- a/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php +++ b/core/modules/content_translation/src/Plugin/migrate/source/I18nQueryTrait.php @@ -2,6 +2,8 @@ namespace Drupal\content_translation\Plugin\migrate\source; +@trigger_error('The ' . __NAMESPACE__ . '\I18nQueryTrait is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait instead. See https://www.drupal.org/node/3439256', E_USER_DEPRECATED); + use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\Row; @@ -10,6 +12,11 @@ /** * Gets an i18n translation from the source database. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait instead. + * + * @see https://www.drupal.org/node/3439256 */ trait I18nQueryTrait { diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php index 3003310e6d83..0aede4ac4a95 100644 --- a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php +++ b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLinkTranslation.php @@ -2,9 +2,9 @@ namespace Drupal\menu_link_content\Plugin\migrate\source\d6; -use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait; -use Drupal\migrate\Row; use Drupal\menu_link_content\Plugin\migrate\source\MenuLink; +use Drupal\migrate\Row; +use Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait; // cspell:ignore mlid diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php b/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php index 85a2a4f61505..fd7228fea9f2 100644 --- a/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php +++ b/core/modules/menu_link_content/src/Plugin/migrate/source/d7/MenuLinkTranslation.php @@ -2,9 +2,9 @@ namespace Drupal\menu_link_content\Plugin\migrate\source\d7; -use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait; -use Drupal\migrate\Row; use Drupal\menu_link_content\Plugin\migrate\source\MenuLink; +use Drupal\migrate\Row; +use Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait; // cspell:ignore mlid objectid textgroup diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/I18nQueryTrait.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/I18nQueryTrait.php new file mode 100644 index 000000000000..c2baab04d422 --- /dev/null +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/I18nQueryTrait.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\migrate_drupal\Plugin\migrate\source; + +use Drupal\migrate\Plugin\MigrateIdMapInterface; +use Drupal\migrate\MigrateException; +use Drupal\migrate\Row; + +// cspell:ignore objectid + +/** + * Gets an i18n translation from the source database. + */ +trait I18nQueryTrait { + + /** + * The i18n string table name. + * + * @var string + */ + protected string $i18nStringTable; + + /** + * Gets the translation for the property not already in the row. + * + * For some i18n migrations there are two translation values, such as a + * translated title and a translated description, that need to be retrieved. + * Since these values are stored in separate rows of the i18nStringTable + * table we get them individually, one in the source plugin query() and the + * other in prepareRow(). The names of the properties varies, for example, + * in BoxTranslation they are 'body' and 'title' whereas in + * MenuLinkTranslation they are 'title' and 'description'. This will save both + * translations to the row. + * + * @param \Drupal\migrate\Row $row + * The current migration row which must include both a 'language' property + * and an 'objectid' property. The 'objectid' is the value for the + * 'objectid' field in the i18n_string table. + * @param string $property_not_in_row + * The name of the property to get the translation for. + * @param string $object_id_name + * The value of the objectid in the i18n table. + * @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map + * The ID map. + * + * @return bool + * FALSE if the property has already been migrated. + * + * @throws \Drupal\migrate\MigrateException + */ + protected function getPropertyNotInRowTranslation(Row $row, string $property_not_in_row, string $object_id_name, MigrateIdMapInterface $id_map): bool { + $language = $row->getSourceProperty('language'); + if (!$language) { + throw new MigrateException('No language found.'); + } + $object_id = $row->getSourceProperty($object_id_name); + if (!$object_id) { + throw new MigrateException('No objectid found.'); + } + + // If this row has been migrated it is a duplicate so skip it. + if ($id_map->lookupDestinationIds([$object_id_name => $object_id, 'language' => $language])) { + return FALSE; + } + + // Save the translation for the property already in the row. + $property_in_row = $row->getSourceProperty('property'); + $row->setSourceProperty($property_in_row . '_translated', $row->getSourceProperty('translation')); + + // Get the translation, if one exists, for the property not already in the + // row. + $query = $this->select($this->i18nStringTable, 'i18n') + ->fields('i18n', ['lid']) + ->condition('i18n.property', $property_not_in_row) + ->condition('i18n.objectid', $object_id); + $query->leftJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]'); + $query->condition('lt.language', $language); + $query->addField('lt', 'translation'); + $results = $query->execute()->fetchAssoc(); + $row->setSourceProperty($property_not_in_row . '_translated', $results['translation'] ?? NULL); + return TRUE; + } + +} diff --git a/core/modules/migrate_drupal/tests/src/Kernel/I18nQueryTraitTest.php b/core/modules/migrate_drupal/tests/src/Kernel/I18nQueryTraitTest.php new file mode 100644 index 000000000000..11668be1c7f8 --- /dev/null +++ b/core/modules/migrate_drupal/tests/src/Kernel/I18nQueryTraitTest.php @@ -0,0 +1,91 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\migrate_drupal\Kernel; + +use Drupal\KernelTests\KernelTestBase; +use Drupal\migrate\Plugin\migrate\source\SourcePluginBase; +use Drupal\migrate\Plugin\MigrationInterface; + +/** + * Tests instantiating migrate source plugins using I18nQueryTrait. + * + * @group migrate_drupal + */ +class I18nQueryTraitTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'block_content', + 'menu_link_content', + 'migrate', + 'migrate_drupal', + 'taxonomy', + ]; + + /** + * Tests instantiating migrate source plugins using I18nQueryTrait. + * + * I18nQueryTrait was originally in the content_translation module, which + * could lead to fatal errors instantiating the source plugins that use it + * when the content_translation module was not installed. + * + * @param string $plugin_id + * The ID of a Migrate source plugin that uses I18nQueryTrait. + * + * @dataProvider providerI18nQueryTraitPlugins + */ + public function testMigrateSourcePluginUsingI18nQueryTraitDiscovery(string $plugin_id): void { + // Namespace for uninstalled module content_translation needs to be removed + // for this test. + $this->disablePsr4ForUninstalledModules(['content_translation']); + + $migration = $this->createMock(MigrationInterface::class); + $this->assertInstanceOf(SourcePluginBase::class, \Drupal::service('plugin.manager.migrate.source')->createInstance($plugin_id, [], $migration)); + } + + /** + * Removes PSR-4 namespaces from class loader for uninstalled modules. + * + * TestRunnerKernel registers namespaces for all modules, including + * uninstalled modules. This method removes the PSR-4 namespace for the list + * of modules passed in after confirming they are all uninstalled. + * + * @param string[] $remove_psr4_modules + * List of machine names of modules that are uninstalled and whose PSR-4 + * namespaces should be removed from the class loader. + */ + protected function disablePsr4ForUninstalledModules(array $remove_psr4_modules): void { + /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */ + $module_list = \Drupal::service('extension.list.module'); + $available_modules = $module_list->getAllAvailableInfo(); + $installed_modules = $module_list->getAllInstalledInfo(); + $prefixes = $this->classLoader->getPrefixesPsr4(); + foreach ($remove_psr4_modules as $module) { + $this->assertArrayHasKey($module, $available_modules); + $this->assertArrayNotHasKey($module, $installed_modules); + if (isset($prefixes["Drupal\\$module\\"])) { + // Cannot actually remove the PSR4 prefix from the class loader, so set + // the path to a wrong location. + $this->classLoader->setPsr4("Drupal\\$module\\", ''); + } + } + } + + /** + * Provides data for testMigrateSourcePluginUsingI18nQueryTraitDiscovery(). + */ + public static function providerI18nQueryTraitPlugins(): array { + return [ + ['d6_box_translation'], + ['d7_block_custom_translation'], + ['d6_menu_link_translation'], + ['d7_menu_link_translation'], + ['d7_term_localized_translation'], + ]; + } + +} diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php index 5ca0dfe7d503..d5adfdc3f3ba 100644 --- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php +++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php @@ -2,8 +2,8 @@ namespace Drupal\taxonomy\Plugin\migrate\source\d7; -use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait; use Drupal\migrate\Row; +use Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait; // cspell:ignore ltlanguage objectid -- GitLab