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 ce592d27fb34ff77a7eab5f947e6ac7bfa2f9957..346b280ca08e18ceb1174ef4a3e1d9536bcbf581 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 29a64920b721d8bacec7bc8ee8393158d4a82e07..f6ea52e84aba71a19914174621dd0dae10d07de6 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 3003310e6d8329305aff694c6d6b900a4dec1870..0aede4ac4a95844cf6d92b901675f5af5b0cbe87 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 85a2a4f61505369d178acd447f0cba2da8c1fc15..fd7228fea9f2b89f734a3c77a7df63a485075b9b 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 0000000000000000000000000000000000000000..c2baab04d4228bacd17e4afde1646d3ae5a050c2 --- /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 0000000000000000000000000000000000000000..11668be1c7f8705efdb5036c3984b19d81a3e89f --- /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 5ca0dfe7d503c3a6ec90d09426f3361ddcdb194b..d5adfdc3f3ba495886d3cfb0c1741f0888805d11 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