summaryrefslogtreecommitdiffstatshomepage
path: root/core/modules/migrate_drupal
diff options
context:
space:
mode:
authorcatch <6915-catch@users.noreply.drupalcode.org>2025-02-03 12:41:48 +0000
committercatch <6915-catch@users.noreply.drupalcode.org>2025-02-03 12:41:48 +0000
commitba9b75f625f257910104219038ab30a0fb032a87 (patch)
tree99aa73cebaf6d19fb0b2aa25c31e8090b6a00396 /core/modules/migrate_drupal
parent380195b0bbe840e87be719ab166bfa1c832e28ad (diff)
downloaddrupal-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.php86
-rw-r--r--core/modules/migrate_drupal/tests/src/Kernel/I18nQueryTraitTest.php91
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'],
+ ];
+ }
+
+}