diff options
author | catch <6915-catch@users.noreply.drupalcode.org> | 2025-02-03 12:41:48 +0000 |
---|---|---|
committer | catch <6915-catch@users.noreply.drupalcode.org> | 2025-02-03 12:41:48 +0000 |
commit | ba9b75f625f257910104219038ab30a0fb032a87 (patch) | |
tree | 99aa73cebaf6d19fb0b2aa25c31e8090b6a00396 /core/modules/migrate_drupal | |
parent | 380195b0bbe840e87be719ab166bfa1c832e28ad (diff) | |
download | drupal-ba9b75f625f257910104219038ab30a0fb032a87.tar.gz drupal-ba9b75f625f257910104219038ab30a0fb032a87.zip |
Issue #3258581 by godotislate, nikolay shapovalov, boromino, quietone, merlin06, benjifisher: Move I18nQueryTrait from content_translation to migrate_drupal
Diffstat (limited to 'core/modules/migrate_drupal')
-rw-r--r-- | core/modules/migrate_drupal/src/Plugin/migrate/source/I18nQueryTrait.php | 86 | ||||
-rw-r--r-- | core/modules/migrate_drupal/tests/src/Kernel/I18nQueryTraitTest.php | 91 |
2 files changed, 177 insertions, 0 deletions
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 00000000000..c2baab04d42 --- /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 00000000000..11668be1c7f --- /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'], + ]; + } + +} |