diff --git a/migrations/multifield.yml b/migrations/multifield.yml new file mode 100644 index 0000000..11fee6d --- /dev/null +++ b/migrations/multifield.yml @@ -0,0 +1,85 @@ +id: multifield +label: Multifield +migration_tags: + - Drupal 7 + - Content + - Multifield Content +deriver: Drupal\paragraphs\Plugin\migrate\MultifieldDeriver +source: + plugin: multifield +process: + id: + - + plugin: migration_lookup + migration: multifield + no_stub: true + source: + - entity_type + - entity_id + - field_name + - delta + - + plugin: default_value + default_value: + - null # ID. + - null # Revision ID. + - null # Language ID. + - + plugin: extract + index: + - 0 + revision_id: + - + plugin: migration_lookup + migration: multifield + no_stub: true + source: + - entity_type + - entity_id + - field_name + - delta + - revision_id + - + plugin: default_value + default_value: + - null # ID. + - null # Revision ID. + - null # Language ID. + - + plugin: extract + index: + - 1 + type: + - + plugin: migration_lookup + migration: multifield_type + no_stub: true + source: field_name + - + plugin: skip_on_empty + method: row + parent_id: entity_id + parent_type: entity_type + parent_field_name: field_name + langcode: + plugin: null_coalesce + source: + - host_language + - language + # In Drupal 9, when an entity translation is the default translation, then + # "content_translation_source" is "und". This is a bit different than Drupal 7 + # Entity Translation, where default_translations are represented with "source" + # (current alias: "source_language") set to an empty string ''. + content_translation_source: + plugin: default_value + source: source_language + default_value: und +destination: + plugin: entity_complete:paragraph + translations: true +migration_dependencies: + required: + - multifield_type + optional: + - d7_field_instance + - multifield_translation_settings diff --git a/migrations/multifield_translation_settings.yml b/migrations/multifield_translation_settings.yml new file mode 100644 index 0000000..d9d719c --- /dev/null +++ b/migrations/multifield_translation_settings.yml @@ -0,0 +1,39 @@ +id: multifield_translation_settings +label: Multifield translation settings +migration_tags: + - Drupal 7 + - Configuration + - Multilingual +deriver: Drupal\paragraphs\Plugin\migrate\MultifieldConfigDeriver +source: + plugin: multifield_translation_settings + constants: + target_entity_type_id: paragraph + default_langcode: current_interface + language_alterable: true +process: + target_entity_type_id: 'constants/target_entity_type_id' + target_bundle: + - + plugin: migration_lookup + migration: multifield_type + no_stub: true + source: field_name + - + plugin: skip_on_empty + method: row + id: + plugin: concat + source: + - target_entity_type_id + - '@target_bundle' + delimiter: '.' + default_langcode: 'constants/default_langcode' + language_alterable: 'constants/language_alterable' + 'third_party_settings/content_translation/enabled': translatable + 'third_party_settings/content_translation/bundle_settings/untranslatable_fields_hide': untranslatable_fields_hide +destination: + plugin: entity:language_content_settings +migration_dependencies: + required: + - multifield_type diff --git a/migrations/multifield_type.yml b/migrations/multifield_type.yml new file mode 100644 index 0000000..34969ce --- /dev/null +++ b/migrations/multifield_type.yml @@ -0,0 +1,25 @@ +id: multifield_type +label: Multifield type +migration_tags: + - Drupal 7 + - Configuration +deriver: Drupal\paragraphs\Plugin\migrate\MultifieldConfigDeriver +source: + plugin: multifield_type +process: + id: field_name + # The ID of the paragraphs types migrated from multifield fields is the name + # of the multifield field, such as 'field_multifield_with_subfields'. If you + # want to remove the "field_" prefix (if any) from the destination type ID and + # migrate into 'multifield_with_subfields' instead, remove the process + # pipeline of the 'id' destination property above, and uncomment the next one. + # id: + # plugin: callback + # callable: + # - 'Drupal\paragraphs\Utility\MultifieldMigration' + # - 'removePrefix' + # source: field_name + label: label + description: description +destination: + plugin: entity:paragraphs_type diff --git a/paragraphs.module b/paragraphs.module index 9d49daa..094b17b 100644 --- a/paragraphs.module +++ b/paragraphs.module @@ -5,6 +5,8 @@ * Contains paragraphs.module */ +use Drupal\Core\Database\Query\AlterableInterface; +use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\Core\Routing\RouteMatchInterface; @@ -13,6 +15,7 @@ use Drupal\paragraphs\Entity\ParagraphsType; use Drupal\paragraphs\MigrationPluginsAlterer; use Drupal\Core\Render\Element; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\paragraphs\Utility\MultifieldMigration; /** * Implements hook_help(). @@ -472,3 +475,15 @@ function template_preprocess_paragraphs_summary(&$variables) { $variables['behaviors'] = $variables['element']['#summary']['behaviors']; $variables['expanded'] = !empty($variables['element']['#expanded']); } + +/** + * Implements hook_query_alter(). + */ +function paragraphs_query_migrate_field_value_alter(AlterableInterface $query) { + if (!$query instanceof SelectInterface) { + return; + } + + // Requires core patch at https://drupal.org/i/3218294 + MultifieldMigration::addCruicalMultifieldFieldProperties($query); +} diff --git a/src/MigrationPluginsAlterer.php b/src/MigrationPluginsAlterer.php index 241ad42..e98eaad 100644 --- a/src/MigrationPluginsAlterer.php +++ b/src/MigrationPluginsAlterer.php @@ -23,6 +23,7 @@ final class MigrationPluginsAlterer { const PARAGRAPHS_ENTITY_TYPE_ID_MAP = [ 'field_collection_item' => 'paragraph', 'paragraphs_item' => 'paragraph', + 'multifield' => 'paragraph', ]; /** @@ -124,6 +125,11 @@ final class MigrationPluginsAlterer { $migration['process'][$process_property] = "paragraphs_$process_property"; break; + case 'multifield': + $migration['source']["paragraphs_$process_property"] = 'paragraph'; + $migration['process'][$process_property] = "paragraphs_$process_property"; + break; + case NULL: $entity_type_process = &$migration['process'][$process_property]; $entity_type_process[] = [ @@ -161,7 +167,7 @@ final class MigrationPluginsAlterer { * @param string|null $source_entity_type_value * The source entity type, or NULL if it wasn't determinable. */ - public function paragraphsMigrationBundleAdjust(array &$migration, $source_entity_type_value) { + public function paragraphsMigrationBundleAdjust(array &$migration, $source_entity_type_value = NULL) { // @see https://www.drupal.org/project/drupal/releases/9.1.4 // @see https://www.drupal.org/project/drupal/issues/2565931 $key = version_compare(\Drupal::VERSION, '9.1.4', '<') @@ -192,9 +198,14 @@ final class MigrationPluginsAlterer { $migration['migration_dependencies']['required'][] = 'd7_paragraphs_type'; break; + case 'multifield': + $migration['migration_dependencies']['required'][] = 'multifield_type'; + break; + case NULL: $migration['migration_dependencies']['optional'][] = 'd7_field_collection_type'; $migration['migration_dependencies']['optional'][] = 'd7_paragraphs_type'; + $migration['migration_dependencies']['optional'][] = 'multifield_type'; break; } } diff --git a/src/Plugin/migrate/MultifieldConfigDeriver.php b/src/Plugin/migrate/MultifieldConfigDeriver.php new file mode 100644 index 0000000..630ef64 --- /dev/null +++ b/src/Plugin/migrate/MultifieldConfigDeriver.php @@ -0,0 +1,231 @@ +basePluginId = $base_plugin_id; + $this->fieldDiscovery = $field_discovery; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $base_plugin_id, + $container->get('migrate_drupal.field_discovery') + ); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + $types = MigrationDeriverTrait::getSourcePlugin('multifield_type'); + $source_plugin = MigrationDeriverTrait::getSourcePlugin($base_plugin_definition['source']['plugin']); + + try { + $types->checkRequirements(); + if ($types !== $source_plugin) { + $source_plugin->checkRequirements(); + } + } + catch (RequirementsException $e) { + return $this->derivatives; + } + + try { + foreach ($types as $row) { + assert($row instanceof Row); + $field_name = $row->getSourceProperty('field_name'); + $derivative_definition = $base_plugin_definition; + $derivative_id = $field_name; + $derivative_definition['label'] = $this->t('@label (@type)', [ + '@label' => $derivative_definition['label'], + '@type' => $row->getSourceProperty('label'), + ]); + $derivative_definition['source']['field_name'] = $field_name; + + $this->hardenDefinition($derivative_definition); + + $this->derivatives[$derivative_id] = $derivative_definition; + } + } + catch (DatabaseExceptionWrapper $e) { + // Once we begin iterating the source plugin it is possible that the + // source tables will not exist. This can happen when + // MigrationPluginManager gathers up the migration definitions but we do + // not actually have a Drupal 7 source database. + } + return $this->derivatives; + } + + /** + * Hardens dependencies and lookup migration IDs of multifield migrations. + * + * @param array $plugin_definition + * A derivative's migration plugin definition. + */ + protected function hardenDefinition(array &$plugin_definition) { + $field_name = $plugin_definition['source']['field_name'] ?? NULL; + if (!$field_name) { + return; + } + + // Harden migrations derived per field name. + $this->hardenDependencies($plugin_definition, static::MIGRATIONS_DERIVED_PER_FIELD_NAME, $field_name); + $this->hardenMigrationLookups($plugin_definition, static::MIGRATIONS_DERIVED_PER_FIELD_NAME, $field_name); + + // Harden migrations derived per source entity type ID, source entity bundle + // and host field name. + $entity_type = $plugin_definition['source']['entity_type'] ?? NULL; + $bundle = $plugin_definition['source']['bundle'] ?? NULL; + if ($entity_type && $bundle) { + $derivative_id = implode(PluginBase::DERIVATIVE_SEPARATOR, [ + $entity_type, + $bundle, + $field_name, + ]); + $this->hardenDependencies($plugin_definition, static::MIGRATIONS_DERIVED_PER_TYPE_BUNDLE_FIELD, $derivative_id); + $this->hardenMigrationLookups($plugin_definition, static::MIGRATIONS_DERIVED_PER_TYPE_BUNDLE_FIELD, $derivative_id); + } + } + + /** + * Hardens dependencies of a multifield migration. + * + * @param array $plugin_definition + * A derivative's migration plugin definition. + * @param string[] $migration_ids_to_harden + * An array of the migration plugin IDs which should be hardened. + * @param string $derivative_id + * The derivative ID to add to the corresponding migration dependencies. + */ + protected function hardenDependencies(array &$plugin_definition, array $migration_ids_to_harden, string $derivative_id) { + foreach (['required', 'optional'] as $dependency_type) { + if (empty($plugin_definition['migration_dependencies'][$dependency_type])) { + continue; + } + + foreach ($plugin_definition['migration_dependencies'][$dependency_type] as $key => $dependency) { + if (in_array($dependency, $migration_ids_to_harden, TRUE)) { + $plugin_definition['migration_dependencies'][$dependency_type][$key] = implode(PluginBase::DERIVATIVE_SEPARATOR, [ + $dependency, + $derivative_id, + ]); + } + } + } + } + + /** + * Adds derivative IDs to the multifield migrations used in migrate lookups. + * + * @param array $plugin_definition + * A migration plugin definition. + * @param string[] $migration_ids_to_harden + * An array of the migration plugin IDs which should be hardened. + * @param string $derivative_id + * The derivative ID. + */ + protected function hardenMigrationLookups(array &$plugin_definition, array $migration_ids_to_harden, string $derivative_id) { + foreach ($plugin_definition['process'] as &$property_process) { + if (!is_array($property_process)) { + continue; + } + + if (isset($property_process['plugin']) && $property_process['plugin'] === 'migration_lookup') { + $this->addDerivativeId($property_process, $migration_ids_to_harden, $derivative_id); + } + else { + foreach ($property_process as &$subprocess) { + if (isset($subprocess['plugin']) && $subprocess['plugin'] === 'migration_lookup') { + $this->addDerivativeId($subprocess, $migration_ids_to_harden, $derivative_id); + } + } + } + } + } + + /** + * Identifies migrations used by migration_lookup process plugins. + * + * @param array $process + * A process configuration array. + * @param string[] $migration_ids_to_harden + * An array of the migration plugin IDs which should be hardened. + * @param string $derivative_id + * The derivative ID. + */ + protected function addDerivativeId(array &$process, array $migration_ids_to_harden, string $derivative_id) { + $process['migration'] = (array) $process['migration']; + foreach ($process['migration'] as $key => $lookup_migration_id) { + if (in_array($lookup_migration_id, $migration_ids_to_harden, TRUE)) { + $process['migration'][$key] = implode(PluginBase::DERIVATIVE_SEPARATOR, [ + $lookup_migration_id, + $derivative_id, + ]); + } + } + } + +} diff --git a/src/Plugin/migrate/MultifieldDeriver.php b/src/Plugin/migrate/MultifieldDeriver.php new file mode 100644 index 0000000..d32db2c --- /dev/null +++ b/src/Plugin/migrate/MultifieldDeriver.php @@ -0,0 +1,91 @@ +checkRequirements(); + $field_instance_source->checkRequirements(); + } + catch (RequirementsException $e) { + return $this->derivatives; + } + + if (!$field_instance_source instanceof DrupalSqlBase) { + return $this->derivatives; + } + + try { + $fields_types_bundles = $field_instance_source->query() + ->condition('fc.type', 'multifield') + ->execute() + ->fetchAll(\PDO::FETCH_ASSOC); + } + catch (DatabaseExceptionWrapper $e) { + $fields_types_bundles = []; + } + + try { + foreach ($fields_types_bundles as $field_instance_data) { + [ + 'field_name' => $field_name, + 'entity_type' => $source_entity_type, + 'bundle' => $source_bundle, + 'data' => $data_serialized, + ] = $field_instance_data; + $derivative_definition = $base_plugin_definition; + $derivative_id = implode(PluginBase::DERIVATIVE_SEPARATOR, [ + $source_entity_type, + $source_bundle, + $field_name, + ]); + $derivative_definition['label'] = $this->t('@label (@type)', [ + '@label' => $derivative_definition['label'], + '@type' => unserialize($data_serialized)['label'] ?? $derivative_id, + ]); + $derivative_definition['source']['field_name'] = $field_name; + $derivative_definition['source']['entity_type'] = $source_entity_type; + $derivative_definition['source']['bundle'] = $source_bundle; + + if ($source_plugin instanceof FieldableEntity) { + $migration = \Drupal::service('plugin.manager.migration') + ->createStubMigration($derivative_definition); + assert($migration instanceof Migration); + $this->fieldDiscovery->addBundleFieldProcesses($migration, 'multifield', $field_name); + $derivative_definition = $migration->getPluginDefinition(); + } + + $this->hardenDefinition($derivative_definition); + + $this->derivatives[$derivative_id] = $derivative_definition; + } + } + catch (DatabaseExceptionWrapper $e) { + // Once we begin iterating the source plugin it is possible that the + // source tables will not exist. This can happen when + // MigrationPluginManager gathers up the migration definitions but we do + // not actually have a Drupal 7 source database. + } + return $this->derivatives; + } + +} diff --git a/src/Plugin/migrate/field/Multifield.php b/src/Plugin/migrate/field/Multifield.php new file mode 100644 index 0000000..b7d557f --- /dev/null +++ b/src/Plugin/migrate/field/Multifield.php @@ -0,0 +1,230 @@ + 'paragraphs_delta_sort', + 'source' => $field_name, + ], + [ + 'plugin' => 'sub_process', + 'process' => [ + 'source_field_name' => [ + 'plugin' => 'default_value', + 'default_value' => $field_name, + ], + 'lookup_result' => [ + [ + 'plugin' => 'migration_lookup', + 'migration' => $lookup_migration, + 'no_stub' => TRUE, + 'source' => [ + 'entity_type', + 'entity_id', + '@source_field_name', + 'delta', + 'revision_id', + 'language', + ], + ], + [ + 'plugin' => 'skip_on_empty', + 'method' => 'process', + ], + ], + 'target_id' => [ + 'plugin' => 'extract', + 'source' => '@lookup_result', + 'index' => [0], + ], + 'target_revision_id' => [ + 'plugin' => 'extract', + 'source' => '@lookup_result', + 'index' => [1], + ], + // This "needs_resave" tells Entity Reference Revisions to not + // force-create new paragraphs revision while a migration adds a new + // revision to the host entity. + 'needs_resave' => [ + 'plugin' => 'default_value', + 'default_value' => FALSE, + ], + ], + ], + ]; + $migration->setProcessOfProperty($field_name, $process); + $dependencies = $migration->getMigrationDependencies(); + $dependencies['required'] = array_unique( + array_merge( + $dependencies['required'], + [$lookup_migration] + ) + ); + $migration->set('migration_dependencies', $dependencies); + } + + /** + * {@inheritdoc} + */ + public function getFieldWidgetMap() { + return [ + 'multifield_default' => 'entity_reference_paragraphs', + ]; + } + + /** + * {@inheritdoc} + */ + public function getFieldFormatterMap() { + return [ + 'multifield_default' => 'entity_reference_revisions_entity_view', + ]; + } + + /** + * {@inheritdoc} + */ + public function alterFieldMigration(MigrationInterface $migration) { + $original_process = $migration->getProcess()['settings'] ?? []; + if (!self::processIsPresent($original_process, 'paragraphs_field_settings')) { + $new_process = array_merge( + $original_process, + [ + [ + 'plugin' => 'paragraphs_field_settings', + 'source_type' => 'multifield', + ], + ] + ); + $migration->mergeProcessOfProperty('settings', $new_process); + } + } + + /** + * {@inheritdoc} + */ + public function alterFieldInstanceMigration(MigrationInterface $migration) { + $original_settings_process = $migration->getProcess()['settings'] ?? []; + if (!self::processIsPresent($original_settings_process, 'multifield_field_instance_settings')) { + $new_process = array_merge( + $original_settings_process, + [ + [ + 'plugin' => 'multifield_field_instance_settings', + ], + ] + ); + $migration->mergeProcessOfProperty('settings', $new_process); + } + + $original_translatable_process = $migration->getProcess()['translatable'] ?? []; + if (!self::processIsPresent($original_translatable_process, 'multifield_field_translatable')) { + $new_process = array_merge( + $original_translatable_process, + [ + [ + 'plugin' => 'multifield_field_translatable', + ], + ] + ); + $migration->mergeProcessOfProperty('translatable', $new_process); + } + + $this->addMultifieldTargetBundleLookup($migration, 'bundle'); + } + + /** + * Adds a multifield target bundle lookup to the given process pipeline. + * + * @param \Drupal\migrate\Plugin\MigrationInterface $migration + * The migration plugin instance. + * @param string $destination_property + * The destination property which identifies the pipeline. + */ + protected function addMultifieldTargetBundleLookup(MigrationInterface $migration, string $destination_property) { + $original_bundle_process = $migration->getProcess()[$destination_property] ?? []; + if (!self::processIsPresent($original_bundle_process, 'target_bundle_lookup')) { + $new_process = array_merge( + $original_bundle_process, + [ + [ + 'plugin' => 'target_bundle_lookup', + 'source_entity_type' => 'entity_type', + 'lookup_migrations' => [ + 'multifield' => 'multifield_type', + ], + 'skip_row_on_missing' => [ + 'multifield' => $this->t('Cannot found the destination paragraphs type of this multifield subfield.'), + ], + ], + ] + ); + $migration->mergeProcessOfProperty($destination_property, $new_process); + } + } + + /** + * Checks whether a migration process is present in the given pipeline. + * + * @param array[] $process_pipeline + * A migration process pipeline (ofa destination property). + * @param string|array[] $plugin + * The ID of the migrate process plugin to check for, or a complete process + * plugin configuration. + * + * @return bool + * TRUE if the plugin is used in the given process pipeline, FALSE if not. + */ + protected static function processIsPresent(array $process_pipeline, $plugin) { + return is_string($plugin) + ? array_reduce($process_pipeline, function (bool $carry, array $process) use ($plugin) { + $carry = $carry || $process['plugin'] === $plugin; + return $carry; + }, FALSE) + : array_reduce($process_pipeline, function (bool $carry, array $process) use ($plugin) { + $carry = $carry || $process === $plugin; + return $carry; + }, FALSE); + } + +} diff --git a/src/Plugin/migrate/process/Multifield.php b/src/Plugin/migrate/process/Multifield.php new file mode 100644 index 0000000..89ebef9 --- /dev/null +++ b/src/Plugin/migrate/process/Multifield.php @@ -0,0 +1,65 @@ +configuration['no_stub'] = TRUE; + $source_field_name = $this->configuration['source_field_name']; + ksort($value); + $source = $this->migration->getSourcePlugin(); + assert($source instanceof DrupalSqlBase); + $field_value = []; + + foreach ($value as $delta => $item) { + $lookup_results = parent::transform( + [ + $item['entity_type'], + $item['entity_id'], + $source_field_name, + $item['delta'], + $item['revision_id'], + $item['language'], + ], + $migrate_executable, + $row, + $destination_property + ); + + $field_value[$delta] = [ + 'target_id' => $lookup_results[0], + 'target_revision_id' => $lookup_results[1], + 'needs_resave' => FALSE, + ]; + } + + return $field_value; + } + +} diff --git a/src/Plugin/migrate/process/MultifieldFieldInstanceSettings.php b/src/Plugin/migrate/process/MultifieldFieldInstanceSettings.php new file mode 100644 index 0000000..4b814af --- /dev/null +++ b/src/Plugin/migrate/process/MultifieldFieldInstanceSettings.php @@ -0,0 +1,87 @@ +migrateLookup = $migrate_lookup; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('migrate.lookup') + ); + } + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + $source_type_is_multifield = $row->getSourceProperty('type') === 'multifield'; + $field_name = $row->getSourceProperty('field_name'); + if (!$source_type_is_multifield || !$field_name) { + return $value; + } + + try { + $lookup_result = $this->migrateLookup->lookup('multifield_type', [$field_name]); + } + catch (\Exception $e) { + $lookup_result = NULL; + } + $destination_bundle = is_array($lookup_result) && reset($lookup_result[0]) + ? reset($lookup_result[0]) + : $field_name; + + return [ + 'handler_settings' => [ + 'negate' => FALSE, + 'target_bundles' => [ + $destination_bundle => $destination_bundle, + ], + ], + ]; + } + +} diff --git a/src/Plugin/migrate/process/MultifieldFieldTranslatable.php b/src/Plugin/migrate/process/MultifieldFieldTranslatable.php new file mode 100644 index 0000000..35bbddc --- /dev/null +++ b/src/Plugin/migrate/process/MultifieldFieldTranslatable.php @@ -0,0 +1,82 @@ +migration = $migration; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $migration + ); + } + + /** + * {@inheritdoc} + */ + public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + $entity_type_is_multifield = $row->getSourceProperty('entity_type') === 'multifield'; + if (!$entity_type_is_multifield || !(($source = $this->migration->getSourcePlugin()) instanceof DrupalSqlBase)) { + return $value; + } + + try { + return $source->getDatabase()->select('field_config', 'fc') + ->fields('fc', ['translatable']) + ->condition('fc.field_name', $row->getSourceProperty('bundle')) + ->execute()->fetchCol()[0] ?? $value; + } + catch (DatabaseExceptionWrapper $e) { + } + return $value; + } + +} diff --git a/src/Plugin/migrate/process/ParagraphsFieldSettings.php b/src/Plugin/migrate/process/ParagraphsFieldSettings.php index 1569a13..cb380bb 100644 --- a/src/Plugin/migrate/process/ParagraphsFieldSettings.php +++ b/src/Plugin/migrate/process/ParagraphsFieldSettings.php @@ -19,7 +19,9 @@ class ParagraphsFieldSettings extends ProcessPluginBase { * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { - if ($row->getSourceProperty('type') == 'paragraphs') { + $source_type = $this->configuration['source_type'] ?? 'paragraphs'; + + if ($row->getSourceProperty('type') == $source_type) { $value['target_type'] = 'paragraph'; } return $value; diff --git a/src/Plugin/migrate/process/TargetBundleLookup.php b/src/Plugin/migrate/process/TargetBundleLookup.php new file mode 100644 index 0000000..054614a --- /dev/null +++ b/src/Plugin/migrate/process/TargetBundleLookup.php @@ -0,0 +1,292 @@ +migration = $migration; + $this->migrateLookup = $migrate_lookup; + $this->entityTypeManager = $entity_type_manager; + $this->migrationPluginManager = $migration_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $migration, + $container->get('migrate.lookup'), + $container->get('entity_type.manager'), + $container->get('plugin.manager.migration') + ); + } + + /** + * {@inheritdoc} + */ + public function transform($bundle, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + $source_entity_type = $this->configuration['source_entity_type'] ?? NULL + ? $row->get($this->configuration['source_entity_type']) + : NULL; + // It is not necessary to define a source entity type ID. But if it is + // defined, try to find the lookup migration IDs. + $lookup_migration_ids = $source_entity_type && !empty($this->configuration['lookup_migrations'][$source_entity_type]) + ? (array) $this->configuration['lookup_migrations'][$source_entity_type] + : []; + $to_be_skipped_if_missing_bundle = array_keys( + array_filter( + $this->configuration['skip_row_on_missing'] ?? [] + ) + ); + + // Discover the appropriate lookup migration IDs when no mapped migrations + // were found and the destination entity type ID is defined. + if ( + empty($lookup_migration_ids) && + ($this->configuration['destination_entity_type'] ?? NULL) && + $destination_entity_type = $row->get($this->configuration['destination_entity_type']) + ) { + if (!($definition = $this->entityTypeManager->getDefinition($destination_entity_type, FALSE))) { + return $bundle; + } + elseif ($bundle_entity_type_id = $definition->getBundleEntityType()) { + $lookup_migration_ids = array_keys( + $this->getBundleEntityTypeMigrations($bundle_entity_type_id) + ); + } + else { + return $bundle; + } + } + + // Perform lookup in the discovered bundle entity migrations (if any). + foreach ($lookup_migration_ids as $lookup_migration_id) { + try { + $lookup_result = $this->migrateLookup->lookup($lookup_migration_id, [$bundle]); + } + catch (\Exception $e) { + $lookup_result = NULL; + } + + if (is_array($lookup_result) && isset($lookup_result[0])) { + $destination_bundle = reset($lookup_result[0]); + break; + } + } + + if ( + !empty($to_be_skipped_if_missing_bundle) && + !isset($destination_bundle) && + $source_entity_type && + in_array($source_entity_type, $to_be_skipped_if_missing_bundle, TRUE) + ) { + if (empty($bundle_entity_type_id) || is_null($this->entityTypeManager->getStorage($bundle_entity_type_id)->load($bundle))) { + $message = is_string($this->configuration['skip_row_on_missing'][$source_entity_type]) + ? $this->configuration['skip_row_on_missing'][$source_entity_type] + : NULL; + throw new MigrateSkipRowException($message); + } + } + + return $destination_bundle ?? $bundle; + } + + /** + * Returns the IDs of entity bundle migrations with matching destination. + * + * @param string $bundle_entity_type_id + * The entity type ID of the bundle entity type. + * + * @return string[] + * The IDs of entity bundle migrations which destination matches the given + * bundle entity ID. + */ + protected function getBundleEntityTypeMigrations($bundle_entity_type_id) { + return array_filter( + $this->migrationPluginManager->createInstances([]), + function (MigrationInterface $migration) use ($bundle_entity_type_id) { + if ($migration->getDestinationConfiguration()['plugin'] !== "entity:$bundle_entity_type_id") { + return FALSE; + } + + // Filter out migrations which don't met their requirements: + // - Migrations which source plugin requirements aren't met. + // - Migrations which destination plugin requirements aren't met. + // - Migrations which dependencies aren't yet executed. + if ($migration instanceof RequirementsInterface) { + try { + $migration->checkRequirements(); + } + catch (RequirementsException $e) { + return FALSE; + } + } + + // Filter out migrations which source or destination ID count does not + // equal to 1. + if ( + count($migration->getSourcePlugin()->getIds()) !== 1 || + count($migration->getDestinationPlugin()->getIds()) !== 1 + ) { + return FALSE; + } + + // Migrations which don't have any rows processed (yet) shouldn't be + // used for lookup. + return $migration->getIdMap()->processedCount(); + } + ); + } + +} diff --git a/src/Plugin/migrate/source/d7/Multifield.php b/src/Plugin/migrate/source/d7/Multifield.php new file mode 100644 index 0000000..7cc9f67 --- /dev/null +++ b/src/Plugin/migrate/source/d7/Multifield.php @@ -0,0 +1,255 @@ +configuration['field_name']) + ? (array) $this->configuration['field_name'] + : MultifieldMigration::getMultifieldFields(); + $entity_type = !empty($this->configuration['entity_type']) + ? $this->configuration['entity_type'] + : NULL; + $bundle = !empty($this->configuration['bundle']) + ? $this->configuration['bundle'] + : NULL; + + $query = NULL; + foreach ($field_names as $field_name) { + $subquery = $this->select($this->getFieldDataTableName($field_name), 't') + // For first, only get the crucial data (we want to reduce the SQL + // packet size). + ->fields('t', [ + 'entity_type', + 'entity_id', + 'revision_id', + 'delta', + 'language', + ]) + ->condition('t.deleted', 0) + ->orderBy('t.revision_id'); + $subquery->leftJoin('field_config', 'fc', 'fc.field_name = :field_name', [ + ':field_name' => $field_name, + ]); + if ($entity_type) { + $subquery->condition('t.entity_type', $entity_type); + } + if ($bundle) { + $subquery->condition('t.bundle', $bundle); + } + + if (!$query instanceof SelectInterface) { + $query = $subquery; + continue; + } + + $query->union($subquery); + } + + // If the host entity is translated, then we want to make sure that the + // default translation's revision gets migrated first. + if ($this->getDatabase()->schema()->tableExists('entity_translation')) { + $query->leftJoin('entity_translation', 'et', 't.entity_type = et.entity_type AND t.entity_id = et.entity_id AND t.revision_id = et.revision_id AND t.language = et.language'); + $query->addField('et', 'source', 'source_language'); + $query->orderBy('et.source'); + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function prepareRow(Row $row) { + [ + 'entity_type' => $host_entity_type, + 'entity_id' => $host_entity_id, + 'revision_id' => $host_entity_revision_id, + 'field_name' => $field_name, + 'delta' => $field_delta, + 'language' => $field_language, + ] = $row->getSource(); + + if ($field_language === LanguageInterface::LANGCODE_NOT_SPECIFIED) { + $default_langcode = ((array) $this->variableGet('language_default', ['language' => 'en']))['language']; + $row->setSourceProperty('host_language', $default_langcode); + } + + foreach ($this->getSubfieldsValues($host_entity_type, $host_entity_id, $host_entity_revision_id, $field_name, $field_delta, $field_language) as $source_property => $source_value) { + $row->setSourceProperty($source_property, $source_value); + } + + return parent::prepareRow($row); + } + + /** + * {@inheritdoc} + */ + public function fields() { + $fields = [ + 'entity_type' => $this->t('The entity type.'), + 'entity_id' => $this->t('The entity identifier.'), + 'field_name' => $this->t('The host field.'), + 'delta' => $this->t('The delta of the current multifield.'), + 'revision_id' => $this->t('The entity revision identifier.'), + 'language' => $this->t('The language code.'), + ]; + + return $fields; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + return [ + 'entity_type' => [ + 'type' => 'string', + 'alias' => 't', + ], + 'entity_id' => [ + 'type' => 'integer', + 'alias' => 't', + ], + 'field_name' => [ + 'type' => 'string', + 'alias' => 'fc', + ], + 'delta' => [ + 'type' => 'integer', + 'alias' => 't', + ], + 'revision_id' => [ + 'type' => 'integer', + 'alias' => 't', + ], + 'language' => [ + 'type' => 'string', + 'alias' => 't', + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function checkRequirements() { + parent::checkRequirements(); + if (empty(MultifieldMigration::getMultifieldFields())) { + throw new RequirementsException( + sprintf( + "No multifield fields were found in the source database." + ) + ); + } + } + + /** + * Returns the table name to use for getting the source values. + * + * @param $field_name + * The multifield's field name. + * + * @return string + * The table name to use for getting the source values. + */ + protected function getFieldDataTableName($field_name) { + return empty($this->configuration['exclude_revisions']) + ? "field_revision_{$field_name}" + : "field_data_{$field_name}"; + } + + /** + * Returns the specified multifield's subfields' configuration. + * + * @param string $field_name + * The multifield's field name. + * + * @return array + * An associative array with the subfields configuration. + */ + protected function getSubfieldConfigurations($field_name) { + $query = $this->select('field_config_instance', 'fci') + ->fields('fci', ['field_id', 'field_name']) + ->fields('fc', ['data']) + ->condition('fci.entity_type', 'multifield') + ->condition('fci.bundle', $field_name) + ->condition('fci.deleted', 0); + $query->innerJoin('field_config', 'fc', 'fci.field_id = fc.id'); + $fields = $query->execute()->fetchAllAssoc('field_name'); + + foreach ($fields as $field_name => $field_data) { + $fields[$field_name]['data'] = unserialize($field_data['data']); + } + + return $fields; + } + + /** + * Returns values of the specified subfield record. + * + * @param string $field_name + * The multifield's field name. + * + * @return array + * An associative array with subfield values. + */ + protected function getSubfieldsValues($host_entity_type, $host_entity_id, $host_entity_revision_id, $field_name, $field_delta, $field_language) { + $field_data = $this->getFieldValues($host_entity_type, $field_name, $host_entity_id, $host_entity_revision_id, $field_language)[$field_delta] ?? NULL; + + // Subfield values returned by FieldableEntity::getFieldValues are + // structured as _. + $subfield_values = []; + foreach ($this->getSubfieldConfigurations($field_name) as $subfield_name => $subfield_config) { + $subfield_storage = $subfield_config['data']['storage'] ?? NULL; + $subfield_value = []; + + if ( + $subfield_storage && + $subfield_storage['type'] === 'field_sql_storage' && + is_array($subfield_storage_info = $subfield_storage['details']['sql']['FIELD_LOAD_CURRENT']["field_data_{$subfield_name}"] ?? NULL) + ) { + foreach (array_keys($subfield_storage_info) as $subfield_prop) { + $subfield_value[$subfield_prop] = $field_data["{$subfield_name}_{$subfield_prop}"] ?? NULL; + } + } + // Trying to fall back to the "value" property – if any. + elseif (array_key_exists("{$subfield_name}_value", $field_data)) { + $subfield_value['value'] = $field_data["{$subfield_name}_value"] ?? NULL; + } + + if (!empty(array_filter($subfield_value))) { + // The explicit "0" key represents the (sub)field value delta. + $subfield_values[$subfield_name] = [ + 0 => $subfield_value, + ]; + } + } + + return $subfield_values; + } + +} diff --git a/src/Plugin/migrate/source/d7/MultifieldTranslationSettings.php b/src/Plugin/migrate/source/d7/MultifieldTranslationSettings.php new file mode 100644 index 0000000..a3fc034 --- /dev/null +++ b/src/Plugin/migrate/source/d7/MultifieldTranslationSettings.php @@ -0,0 +1,112 @@ +contentTranslationInstalled = $content_translation_is_installed; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $migration, + $container->get('state'), + $container->get('entity_type.manager'), + $container->get('module_handler')->moduleExists('content_translation') + ); + } + + /** + * {@inheritdoc} + */ + protected function initializeIterator() { + $source_records = []; + foreach (parent::initializeIterator() as $item) { + $item += [ + 'untranslatable_fields_hide' => 1, + ]; + + // If Content Translation isn't installed on the destination, then the + // content_translation related third party settings would cause schema + // errors. + if (!$this->contentTranslationInstalled) { + unset($item['translatable']); + unset($item['untranslatable_fields_hide']); + } + + $source_records[] = $item; + } + return new \ArrayIterator($source_records); + } + + /** + * {@inheritdoc} + */ + public function fields() { + return parent::fields() + [ + 'untranslatable_fields_hide' => $this->t('Whether the untranslatable fields are hidden on the translation edit form.'), + ]; + } + + /** + * {@inheritdoc} + */ + public function checkRequirements() { + parent::checkRequirements(); + + if (!$this->moduleExists('entity_translation')) { + throw new RequirementsException('The Entity Translation module is not enabled in the source site.', [ + 'source_module' => 'entity_translation', + ]); + } + } + +} diff --git a/src/Plugin/migrate/source/d7/MultifieldType.php b/src/Plugin/migrate/source/d7/MultifieldType.php new file mode 100644 index 0000000..c727051 --- /dev/null +++ b/src/Plugin/migrate/source/d7/MultifieldType.php @@ -0,0 +1,125 @@ +configuration['field_name'] ?? NULL; + $query = parent::query(); + $query->condition('fc.type', 'multifield'); + if ($field_name) { + $query->condition('fc.field_name', $field_name); + } + return $query; + } + + /** + * {@inheritdoc} + */ + protected function initializeIterator() { + $source_records = []; + foreach (parent::initializeIterator() as $item) { + if (!empty($source_records[$item['field_name']])) { + continue; + } + $source_records[$item['field_name']] = $item; + } + return new \ArrayIterator($source_records); + } + + /** + * {@inheritdoc} + */ + public function prepareRow(Row $row, $keep = TRUE) { + // Add label and description. + foreach ($this->getMetadata($row->getSourceProperty('field_name')) as $source_property => $source_property_value) { + $row->setSourceProperty($source_property, $source_property_value); + } + + return parent::prepareRow($row); + } + + /** + * {@inheritdoc} + */ + public function fields() { + return parent::fields() + [ + 'label' => $this->t('The label of the multifield.'), + 'description' => $this->t('The description of the multifield.'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + return [ + 'field_name' => [ + 'type' => 'string', + 'alias' => 'fc', + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function doCount() { + return (int) $this->initializeIterator()->count(); + } + + /** + * Returns a label and a description for the actual multifield. + * + * @param string $field_name + * The field's machine name. + * + * @return string[] + * An array with label (at label key) and description (at description key). + */ + protected function getMetadata($field_name) :array { + $field_instance_data = $this->select('field_config_instance', 'fci') + ->fields('fci', ['data']) + ->condition('fci.field_name', $field_name) + ->execute() + ->fetchCol(); + foreach ($field_instance_data as $data_serialized) { + $data = unserialize($data_serialized); + $labels[] = $data['label']; + $descriptions[] = $data['description']; + } + + if (empty($non_empty_descriptions = array_filter($descriptions))) { + // Every single description we discovered is empty – lets return the first + // label. + return [ + 'label' => $labels[0], + 'description' => $descriptions[0], + ]; + } + + // Return the first label - description pair where the description is not + // empty. + return [ + 'label' => $labels[key($non_empty_descriptions)], + 'description' => $descriptions[key($non_empty_descriptions)], + ]; + } + +} diff --git a/src/Utility/MultifieldMigration.php b/src/Utility/MultifieldMigration.php new file mode 100644 index 0000000..8c3dbba --- /dev/null +++ b/src/Utility/MultifieldMigration.php @@ -0,0 +1,119 @@ +moduleExists('migrate_drupal')) { + return self::$multifields = []; + } + + try { + $multifield_type_source_plugin = MigrationDeriverTrait::getSourcePlugin('multifield_type'); + } + catch (PluginNotFoundException $e) { + // If multifield type plugin requirements aren't met, then the plugin + // manager throws a plugin not found exception. + return self::$multifields = []; + } + + self::$multifields = array_map( + function ($mf_row) { + assert($mf_row instanceof Row); + return $mf_row->getSourceProperty('field_name'); + }, + iterator_to_array( + $multifield_type_source_plugin, + FALSE + ) + ); + } + + return self::$multifields; + } + + /** + * @param \Drupal\Core\Database\Query\SelectInterface $query + */ + public static function addCruicalMultifieldFieldProperties(SelectInterface $query) { + if (empty(self::getMultifieldFields())) { + return; + } + + $tables = $query->getTables(); + + // Table alias is not the same as the one used in + if (!isset($tables['t']) || count($tables) !== 1) { + return; + } + + if ($tables['t']['table'] instanceof SelectInterface) { + return; + } + + $table_name = $tables['t']['table']; + $field_name = self::removePrefix( + $table_name, + ['field_data_', 'field_revision_'] + ); + + // Target table is not a DB table which contains field values. + if ($table_name === $field_name) { + return; + } + + // The field of the target table is not a multifield field. + if (!in_array($field_name, self::getMultifieldFields())) { + return; + } + + foreach (['entity_type', 'entity_id', 'bundle', 'delta', 'revision_id', 'language'] as $property) { + $query->addField( + 't', + $property, + "{$field_name}_$property" + ); + } + } + + /** + * Removes the given prefix from a string if present. + * + * @param string $string + * The string to process. + * @param string|string[] $prefix + * The prefix to remove, defaults to 'field_' + * + * @return string + * A string without the first occurrence of the given prefix. + */ + public static function removePrefix($string, $prefix = 'field_') { + $patterns = array_map(function ($prefix) { + return '/^' . preg_quote($prefix) . '/'; + }, (array) $prefix); + return preg_replace($patterns, '', $string); + } + +} diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture.php b/tests/fixtures/drupal7_multifield_on_core_fixture.php new file mode 100644 index 0000000..f4616a5 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture.php @@ -0,0 +1,36 @@ +insert('entity_translation') +->fields(array( + 'entity_type', + 'entity_id', + 'revision_id', + 'language', + 'source', + 'uid', + 'status', + 'translate', + 'created', + 'changed', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'source' => '', + 'uid' => '1', + 'status' => '1', + 'translate' => '0', + 'created' => '1622704056', + 'changed' => '1622704056', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'source' => 'is', + 'uid' => '1', + 'status' => '1', + 'translate' => '0', + 'created' => '1622704117', + 'changed' => '1622704117', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_config.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_config.php new file mode 100644 index 0000000..825365d --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_config.php @@ -0,0 +1,109 @@ +select('field_config', 'fc') + ->fields('fc', ['data']) + ->condition('fc.id', $id) + ->execute() + ->fetchCol()[0]; + + $data = unserialize($data_serialized); + assert(is_array($data)); + $data['settings']['entity_translation_sync'] = FALSE; + + $connection->update('field_config') + ->condition('id', $id) + ->fields(['data' => serialize($data)]) + ->execute(); +} + +$connection->insert('field_config') +->fields(array( + 'id', + 'field_name', + 'type', + 'module', + 'active', + 'storage_type', + 'storage_module', + 'storage_active', + 'locked', + 'data', + 'cardinality', + 'translatable', + 'deleted', +)) +->values(array( + 'id' => '156', + 'field_name' => 'field_multifield_w_text_fields', + 'type' => 'multifield', + 'module' => 'multifield', + 'active' => '1', + 'storage_type' => 'field_sql_storage', + 'storage_module' => 'field_sql_storage', + 'storage_active' => '1', + 'locked' => '0', + 'data' => 'a:7:{s:12:"translatable";i:1;s:12:"entity_types";a:0:{}s:8:"settings";a:2:{s:16:"hide_blank_items";i:1;s:23:"entity_translation_sync";a:1:{i:0;s:2:"id";}}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:41:"field_data_field_multifield_w_text_fields";a:6:{s:22:"field_text_plain_value";s:53:"field_multifield_w_text_fields_field_text_plain_value";s:23:"field_text_plain_format";s:54:"field_multifield_w_text_fields_field_text_plain_format";s:29:"field_text_sum_filtered_value";s:60:"field_multifield_w_text_fields_field_text_sum_filtered_value";s:31:"field_text_sum_filtered_summary";s:62:"field_multifield_w_text_fields_field_text_sum_filtered_summary";s:30:"field_text_sum_filtered_format";s:61:"field_multifield_w_text_fields_field_text_sum_filtered_format";s:2:"id";s:33:"field_multifield_w_text_fields_id";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:45:"field_revision_field_multifield_w_text_fields";a:6:{s:22:"field_text_plain_value";s:53:"field_multifield_w_text_fields_field_text_plain_value";s:23:"field_text_plain_format";s:54:"field_multifield_w_text_fields_field_text_plain_format";s:29:"field_text_sum_filtered_value";s:60:"field_multifield_w_text_fields_field_text_sum_filtered_value";s:31:"field_text_sum_filtered_summary";s:62:"field_multifield_w_text_fields_field_text_sum_filtered_summary";s:30:"field_text_sum_filtered_format";s:61:"field_multifield_w_text_fields_field_text_sum_filtered_format";s:2:"id";s:33:"field_multifield_w_text_fields_id";}}}}}s:12:"foreign keys";a:2:{s:23:"field_text_plain_format";a:2:{s:5:"table";s:13:"filter_format";s:7:"columns";a:1:{s:23:"field_text_plain_format";s:6:"format";}}s:30:"field_text_sum_filtered_format";a:2:{s:5:"table";s:13:"filter_format";s:7:"columns";a:1:{s:30:"field_text_sum_filtered_format";s:6:"format";}}}s:7:"indexes";a:3:{s:23:"field_text_plain_format";a:1:{i:0;s:23:"field_text_plain_format";}s:30:"field_text_sum_filtered_format";a:1:{i:0;s:30:"field_text_sum_filtered_format";}s:2:"id";a:1:{i:0;s:2:"id";}}s:2:"id";s:3:"156";}', + 'cardinality' => '-1', + 'translatable' => '1', + 'deleted' => '0', +)) +->values(array( + 'id' => '157', + 'field_name' => 'field_multifield_complex_fields', + 'type' => 'multifield', + 'module' => 'multifield', + 'active' => '1', + 'storage_type' => 'field_sql_storage', + 'storage_module' => 'field_sql_storage', + 'storage_active' => '1', + 'locked' => '0', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:2:{s:16:"hide_blank_items";i:1;s:23:"entity_translation_sync";a:1:{i:0;s:2:"id";}}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:0:{}}s:12:"foreign keys";a:1:{s:14:"field_tags_tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:14:"field_tags_tid";s:3:"tid";}}}s:7:"indexes";a:2:{s:14:"field_tags_tid";a:1:{i:0;s:14:"field_tags_tid";}s:2:"id";a:1:{i:0;s:2:"id";}}s:2:"id";s:3:"157";}', + 'cardinality' => '1', + 'translatable' => '0', + 'deleted' => '0', +)) +->values(array( + 'id' => '158', + 'field_name' => 'field_deleted_multifield', + 'type' => 'multifield', + 'module' => 'multifield', + 'active' => '1', + 'storage_type' => 'field_sql_storage', + 'storage_module' => 'field_sql_storage', + 'storage_active' => '1', + 'locked' => '0', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:2:{s:16:"hide_blank_items";i:1;s:23:"entity_translation_sync";a:1:{i:0;s:2:"id";}}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:0:{}}s:12:"foreign keys";a:1:{s:17:"field_text_format";a:2:{s:5:"table";s:13:"filter_format";s:7:"columns";a:1:{s:17:"field_text_format";s:6:"format";}}}s:7:"indexes";a:3:{s:17:"field_text_format";a:1:{i:0;s:17:"field_text_format";}s:37:"field_subfield_of_deleted_multi_value";a:1:{i:0;s:37:"field_subfield_of_deleted_multi_value";}s:2:"id";a:1:{i:0;s:2:"id";}}s:2:"id";s:3:"158";}', + 'cardinality' => '1', + 'translatable' => '0', + 'deleted' => '1', +)) +->values(array( + 'id' => '159', + 'field_name' => 'field_subfield_of_deleted_multi', + 'type' => 'list_boolean', + 'module' => 'list', + 'active' => '1', + 'storage_type' => 'field_sql_storage', + 'storage_module' => 'field_sql_storage', + 'storage_active' => '1', + 'locked' => '0', + 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:2:{i:0;s:0:"";i:1;s:0:"";}s:23:"allowed_values_function";s:0:"";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:42:"field_data_field_subfield_of_deleted_multi";a:1:{s:5:"value";s:37:"field_subfield_of_deleted_multi_value";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:46:"field_revision_field_subfield_of_deleted_multi";a:1:{s:5:"value";s:37:"field_subfield_of_deleted_multi_value";}}}}}s:12:"foreign keys";a:0:{}s:7:"indexes";a:1:{s:5:"value";a:1:{i:0;s:5:"value";}}s:2:"id";s:3:"159";}', + 'cardinality' => '1', + 'translatable' => '0', + 'deleted' => '1', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_config_instance.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_config_instance.php new file mode 100644 index 0000000..820db80 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_config_instance.php @@ -0,0 +1,159 @@ +insert('field_config_instance') +->fields(array( + 'id', + 'field_id', + 'field_name', + 'entity_type', + 'bundle', + 'data', + 'deleted', +)) +->values(array( + 'id' => '188', + 'field_id' => '1', + 'field_name' => 'comment_body', + 'entity_type' => 'comment', + 'bundle' => 'comment_node_type_with_multifields', + 'data' => 'a:6:{s:5:"label";s:7:"Comment";s:8:"settings";a:3:{s:15:"text_processing";i:1;s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:8:"required";b:1;s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:6:"weight";i:0;s:8:"settings";a:0:{}s:6:"module";s:4:"text";}}s:6:"widget";a:4:{s:4:"type";s:13:"text_textarea";s:8:"settings";a:1:{s:4:"rows";i:5;}s:6:"weight";i:0;s:6:"module";s:4:"text";}s:11:"description";s:0:"";}', + 'deleted' => '0', +)) +->values(array( + 'id' => '189', + 'field_id' => '2', + 'field_name' => 'body', + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";s:2:"-4";s:6:"module";s:4:"text";}s:8:"settings";a:4:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}', + 'deleted' => '0', +)) +->values(array( + 'id' => '190', + 'field_id' => '156', + 'field_name' => 'field_multifield_w_text_fields', + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'data' => 'a:7:{s:5:"label";s:27:"Multifield with text fields";s:6:"widget";a:5:{s:6:"weight";s:2:"-3";s:4:"type";s:18:"multifield_default";s:6:"module";s:10:"multifield";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:18:"multifield_default";s:8:"settings";a:1:{s:9:"view_mode";s:7:"default";}s:6:"module";s:10:"multifield";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '191', + 'field_id' => '157', + 'field_name' => 'field_multifield_complex_fields', + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'data' => 'a:7:{s:5:"label";s:30:"Multifield with complex fields";s:6:"widget";a:5:{s:6:"weight";s:2:"-2";s:4:"type";s:18:"multifield_default";s:6:"module";s:10:"multifield";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:18:"multifield_default";s:8:"settings";a:1:{s:9:"view_mode";s:7:"default";}s:6:"module";s:10:"multifield";s:6:"weight";i:2;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '192', + 'field_id' => '26', + 'field_name' => 'field_text_plain', + 'entity_type' => 'multifield', + 'bundle' => 'field_multifield_w_text_fields', + 'data' => 'a:7:{s:5:"label";s:10:"Text plain";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:14:"text_textfield";s:6:"module";s:4:"text";s:6:"active";i:1;s:8:"settings";a:1:{s:4:"size";s:2:"60";}}s:8:"settings";a:3:{s:15:"text_processing";s:1:"0";s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '193', + 'field_id' => '33', + 'field_name' => 'field_text_sum_filtered', + 'entity_type' => 'multifield', + 'bundle' => 'field_multifield_w_text_fields', + 'data' => 'a:7:{s:5:"label";s:21:"Text summary filtered";s:6:"widget";a:5:{s:6:"weight";s:1:"4";s:4:"type";s:26:"text_textarea_with_summary";s:6:"module";s:4:"text";s:6:"active";i:1;s:8:"settings";a:2:{s:4:"rows";s:2:"20";s:12:"summary_rows";i:5;}}s:8:"settings";a:4:{s:15:"text_processing";s:1:"1";s:15:"display_summary";i:0;s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '194', + 'field_id' => '3', + 'field_name' => 'field_tags', + 'entity_type' => 'multifield', + 'bundle' => 'field_multifield_complex_fields', + 'data' => 'a:7:{s:5:"label";s:4:"Tags";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:15:"options_buttons";s:6:"module";s:7:"options";s:6:"active";i:1;s:8:"settings";a:0:{}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:33:"i18n_taxonomy_term_reference_link";s:8:"settings";a:0:{}s:6:"module";s:13:"i18n_taxonomy";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '195', + 'field_id' => '9', + 'field_name' => 'field_date', + 'entity_type' => 'multifield', + 'bundle' => 'field_multifield_complex_fields', + 'data' => 'a:6:{s:5:"label";s:4:"Date";s:6:"widget";a:5:{s:6:"weight";s:1:"4";s:4:"type";s:11:"date_select";s:6:"module";s:4:"date";s:6:"active";i:1;s:8:"settings";a:7:{s:12:"input_format";s:13:"m/d/Y - H:i:s";s:19:"input_format_custom";s:0:"";s:10:"year_range";s:5:"-3:+3";s:9:"increment";s:2:"15";s:14:"label_position";s:5:"above";s:10:"text_parts";a:0:{}s:11:"no_fieldset";i:0;}}s:8:"settings";a:6:{s:13:"default_value";s:3:"now";s:18:"default_value_code";s:0:"";s:14:"default_value2";s:4:"same";s:19:"default_value_code2";s:0:"";s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"date_default";s:8:"settings";a:6:{s:11:"format_type";s:4:"long";s:15:"multiple_number";s:0:"";s:13:"multiple_from";s:0:"";s:11:"multiple_to";s:0:"";s:6:"fromto";s:4:"both";s:19:"show_remaining_days";b:0;}s:6:"module";s:4:"date";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";}', + 'deleted' => '0', +)) +->values(array( + 'id' => '196', + 'field_id' => '15', + 'field_name' => 'field_link', + 'entity_type' => 'multifield', + 'bundle' => 'field_multifield_complex_fields', + 'data' => 'a:7:{s:5:"label";s:4:"Link";s:6:"widget";a:5:{s:6:"weight";s:1:"6";s:4:"type";s:10:"link_field";s:6:"module";s:4:"link";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:13:{s:12:"absolute_url";i:1;s:12:"validate_url";i:1;s:3:"url";i:0;s:5:"title";s:8:"optional";s:11:"title_value";s:0:"";s:27:"title_label_use_field_label";i:0;s:15:"title_maxlength";s:3:"128";s:7:"display";a:1:{s:10:"url_cutoff";s:2:"80";}s:10:"attributes";a:6:{s:6:"target";s:7:"default";s:3:"rel";s:0:"";s:18:"configurable_class";i:0;s:5:"class";s:0:"";s:18:"configurable_title";i:0;s:5:"title";s:0:"";}s:10:"rel_remove";s:7:"default";s:13:"enable_tokens";i:1;s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"link_default";s:8:"settings";a:0:{}s:6:"module";s:4:"link";s:6:"weight";i:2;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '197', + 'field_id' => '42', + 'field_name' => 'name_field', + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'data' => 'a:6:{s:5:"label";s:4:"Name";s:11:"description";s:0:"";s:8:"required";b:1;s:8:"settings";a:4:{s:15:"text_processing";i:0;s:10:"hide_label";a:2:{s:4:"page";b:0;s:6:"entity";b:0;}s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:6:"widget";a:4:{s:6:"weight";s:2:"-5";s:4:"type";s:14:"text_textfield";s:8:"settings";a:1:{s:4:"size";i:60;}s:6:"module";s:4:"text";}s:7:"display";a:1:{s:7:"default";a:4:{s:4:"type";s:6:"hidden";s:5:"label";s:5:"above";s:8:"settings";a:0:{}s:6:"weight";i:0;}}}', + 'deleted' => '0', +)) +->values(array( + 'id' => '198', + 'field_id' => '43', + 'field_name' => 'description_field', + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'data' => 'a:6:{s:8:"required";b:0;s:5:"label";s:11:"Description";s:11:"description";s:0:"";s:8:"settings";a:5:{s:15:"text_processing";i:1;s:10:"hide_label";a:2:{s:4:"page";b:0;s:6:"entity";b:0;}s:15:"display_summary";i:0;s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:6:"widget";a:4:{s:6:"weight";s:2:"-5";s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"module";s:4:"text";}s:7:"display";a:1:{s:7:"default";a:4:{s:4:"type";s:6:"hidden";s:5:"label";s:5:"above";s:8:"settings";a:0:{}s:6:"weight";i:1;}}}', + 'deleted' => '0', +)) +->values(array( + 'id' => '199', + 'field_id' => '156', + 'field_name' => 'field_multifield_w_text_fields', + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'data' => 'a:7:{s:5:"label";s:27:"Multifield with text fields";s:6:"widget";a:5:{s:6:"weight";s:1:"7";s:4:"type";s:18:"multifield_default";s:6:"module";s:10:"multifield";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:18:"multifield_default";s:8:"settings";a:1:{s:9:"view_mode";s:7:"default";}s:6:"module";s:10:"multifield";s:6:"weight";i:2;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '0', +)) +->values(array( + 'id' => '200', + 'field_id' => '158', + 'field_name' => 'field_deleted_multifield', + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'data' => 'a:7:{s:5:"label";s:18:"Deleted multifield";s:6:"widget";a:5:{s:6:"weight";s:1:"8";s:4:"type";s:18:"multifield_default";s:6:"module";s:10:"multifield";s:6:"active";i:0;s:8:"settings";a:0:{}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:18:"multifield_default";s:8:"settings";a:1:{s:9:"view_mode";s:7:"default";}s:6:"module";s:10:"multifield";s:6:"weight";i:3;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '1', +)) +->values(array( + 'id' => '201', + 'field_id' => '159', + 'field_name' => 'field_subfield_of_deleted_multi', + 'entity_type' => 'multifield', + 'bundle' => 'field_deleted_multifield', + 'data' => 'a:7:{s:5:"label";s:25:"Subfield of deleted multi";s:6:"widget";a:5:{s:6:"weight";s:1:"1";s:4:"type";s:13:"options_onoff";s:6:"module";s:7:"options";s:6:"active";i:1;s:8:"settings";a:1:{s:13:"display_label";i:0;}}s:8:"settings";a:2:{s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"list_default";s:8:"settings";a:0:{}s:6:"module";s:4:"list";s:6:"weight";i:0;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";a:1:{i:0;a:1:{s:5:"value";i:0;}}}', + 'deleted' => '1', +)) +->values(array( + 'id' => '202', + 'field_id' => '21', + 'field_name' => 'field_text', + 'entity_type' => 'multifield', + 'bundle' => 'field_deleted_multifield', + 'data' => 'a:7:{s:5:"label";s:4:"Text";s:6:"widget";a:5:{s:6:"weight";s:1:"3";s:4:"type";s:14:"text_textfield";s:6:"module";s:4:"text";s:6:"active";i:1;s:8:"settings";a:1:{s:4:"size";s:2:"60";}}s:8:"settings";a:3:{s:15:"text_processing";s:1:"0";s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:1;}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', + 'deleted' => '1', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_description_field.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_description_field.php new file mode 100644 index 0000000..dd1de33 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_description_field.php @@ -0,0 +1,51 @@ +insert('field_data_description_field') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'description_field_value', + 'description_field_summary', + 'description_field_format', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'delta' => '0', + 'description_field_value' => 'Multifield term description [FR]', + 'description_field_summary' => '', + 'description_field_format' => 'filtered_html', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'delta' => '0', + 'description_field_value' => 'Multifield term description [IS - default]', + 'description_field_summary' => '', + 'description_field_format' => 'filtered_html', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_field_multifield_complex_fields.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_field_multifield_complex_fields.php new file mode 100644 index 0000000..213fbf8 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_field_multifield_complex_fields.php @@ -0,0 +1,161 @@ +schema()->createTable('field_data_field_multifield_complex_fields', array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_multifield_complex_fields_field_tags_tid' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_multifield_complex_fields_field_date_value' => array( + 'type' => 'datetime', + 'not null' => FALSE, + 'mysql_type' => 'datetime', + 'pgsql_type' => 'timestamp without time zone', + 'sqlite_type' => 'varchar', + 'sqlsrv_type' => 'smalldatetime', + ), + 'field_multifield_complex_fields_field_link_url' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '2048', + ), + 'field_multifield_complex_fields_field_link_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_complex_fields_field_link_attributes' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'medium', + ), + 'field_multifield_complex_fields_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'field_multifield_complex_fields_field_tags_tid' => array( + 'field_multifield_complex_fields_field_tags_tid', + ), + 'field_multifield_complex_fields_id' => array( + 'field_multifield_complex_fields_id', + ), + ), + 'mysql_character_set' => 'utf8', +)); + +$connection->insert('field_data_field_multifield_complex_fields') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_multifield_complex_fields_field_tags_tid', + 'field_multifield_complex_fields_field_date_value', + 'field_multifield_complex_fields_field_link_url', + 'field_multifield_complex_fields_field_link_title', + 'field_multifield_complex_fields_field_link_attributes', + 'field_multifield_complex_fields_id', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '120', + 'language' => 'und', + 'delta' => '0', + 'field_multifield_complex_fields_field_tags_tid' => '14', + 'field_multifield_complex_fields_field_date_value' => '2021-06-03 08:15:00', + 'field_multifield_complex_fields_field_link_url' => 'https://www.drupal.org', + 'field_multifield_complex_fields_field_link_title' => 'Drupal org [rev2]', + 'field_multifield_complex_fields_field_link_attributes' => 'a:0:{}', + 'field_multifield_complex_fields_id' => '4', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_field_multifield_w_text_fields.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_field_multifield_w_text_fields.php new file mode 100644 index 0000000..299896a --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_field_multifield_w_text_fields.php @@ -0,0 +1,205 @@ +schema()->createTable('field_data_field_multifield_w_text_fields', array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_multifield_w_text_fields_field_text_plain_value' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_w_text_fields_field_text_plain_format' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_w_text_fields_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'field_multifield_w_text_fields_field_text_plain_format' => array( + 'field_multifield_w_text_fields_field_text_plain_format', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => array( + 'field_multifield_w_text_fields_field_text_sum_filtered_format', + ), + 'field_multifield_w_text_fields_id' => array( + 'field_multifield_w_text_fields_id', + ), + ), + 'mysql_character_set' => 'utf8', +)); + +$connection->insert('field_data_field_multifield_w_text_fields') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_multifield_w_text_fields_field_text_plain_value', + 'field_multifield_w_text_fields_field_text_plain_format', + 'field_multifield_w_text_fields_field_text_sum_filtered_value', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary', + 'field_multifield_w_text_fields_field_text_sum_filtered_format', + 'field_multifield_w_text_fields_id', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '120', + 'language' => 'en', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Content with multifields text plain copy [delta0] [rev2]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => 'Content with multifields text summary copy [delta0] [rev2]', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '2', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '120', + 'language' => 'en', + 'delta' => '1', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Content with multifields text plain copy [delta1] [rev2]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => 'Content with multifields text summary copy [delta1] [rev2]', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '3', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Multifield term "text plain" copy [FR]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => "Multifield term \"text summary filtered\" summary [FR]\r\n\r\nMultifield term \"text summary filtered\" copy [FR]", + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '1', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Multifield term "text plain" copy [IS - default]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => "Multifield term \"text summary filtered\" summary [IS - default]\r\n\r\nMultifield term \"text summary filtered\" copy [IS - default]", + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '1', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_name_field.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_name_field.php new file mode 100644 index 0000000..c727117 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_data_name_field.php @@ -0,0 +1,48 @@ +insert('field_data_name_field') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'name_field_value', + 'name_field_format', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'delta' => '0', + 'name_field_value' => 'Multifield term [FR]', + 'name_field_format' => NULL, +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'delta' => '0', + 'name_field_value' => 'Multifield term [IS - default]', + 'name_field_format' => NULL, +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_description_field.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_description_field.php new file mode 100644 index 0000000..1949a6d --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_description_field.php @@ -0,0 +1,51 @@ +insert('field_revision_description_field') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'description_field_value', + 'description_field_summary', + 'description_field_format', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'delta' => '0', + 'description_field_value' => 'Multifield term description [FR]', + 'description_field_summary' => '', + 'description_field_format' => 'filtered_html', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'delta' => '0', + 'description_field_value' => 'Multifield term description [IS - default]', + 'description_field_summary' => '', + 'description_field_format' => 'filtered_html', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_field_multifield_complex_fields.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_field_multifield_complex_fields.php new file mode 100644 index 0000000..6ba3b9f --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_field_multifield_complex_fields.php @@ -0,0 +1,177 @@ +schema()->createTable('field_revision_field_multifield_complex_fields', array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_multifield_complex_fields_field_tags_tid' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_multifield_complex_fields_field_date_value' => array( + 'type' => 'datetime', + 'not null' => FALSE, + 'mysql_type' => 'datetime', + 'pgsql_type' => 'timestamp without time zone', + 'sqlite_type' => 'varchar', + 'sqlsrv_type' => 'smalldatetime', + ), + 'field_multifield_complex_fields_field_link_url' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '2048', + ), + 'field_multifield_complex_fields_field_link_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_complex_fields_field_link_attributes' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'medium', + ), + 'field_multifield_complex_fields_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'field_multifield_complex_fields_field_tags_tid' => array( + 'field_multifield_complex_fields_field_tags_tid', + ), + 'field_multifield_complex_fields_id' => array( + 'field_multifield_complex_fields_id', + ), + ), + 'mysql_character_set' => 'utf8', +)); + +$connection->insert('field_revision_field_multifield_complex_fields') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_multifield_complex_fields_field_tags_tid', + 'field_multifield_complex_fields_field_date_value', + 'field_multifield_complex_fields_field_link_url', + 'field_multifield_complex_fields_field_link_title', + 'field_multifield_complex_fields_field_link_attributes', + 'field_multifield_complex_fields_id', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '119', + 'language' => 'und', + 'delta' => '0', + 'field_multifield_complex_fields_field_tags_tid' => '11', + 'field_multifield_complex_fields_field_date_value' => '2021-06-03 07:15:00', + 'field_multifield_complex_fields_field_link_url' => 'https://www.drupal.org', + 'field_multifield_complex_fields_field_link_title' => 'Drupal org [rev1]', + 'field_multifield_complex_fields_field_link_attributes' => 'a:0:{}', + 'field_multifield_complex_fields_id' => '4', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '120', + 'language' => 'und', + 'delta' => '0', + 'field_multifield_complex_fields_field_tags_tid' => '14', + 'field_multifield_complex_fields_field_date_value' => '2021-06-03 08:15:00', + 'field_multifield_complex_fields_field_link_url' => 'https://www.drupal.org', + 'field_multifield_complex_fields_field_link_title' => 'Drupal org [rev2]', + 'field_multifield_complex_fields_field_link_attributes' => 'a:0:{}', + 'field_multifield_complex_fields_id' => '4', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_field_multifield_w_text_fields.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_field_multifield_w_text_fields.php new file mode 100644 index 0000000..18f9c42 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_field_multifield_w_text_fields.php @@ -0,0 +1,236 @@ +schema()->createTable('field_revision_field_multifield_w_text_fields', array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_multifield_w_text_fields_field_text_plain_value' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_w_text_fields_field_text_plain_format' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + ), + 'field_multifield_w_text_fields_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'field_multifield_w_text_fields_field_text_plain_format' => array( + 'field_multifield_w_text_fields_field_text_plain_format', + ), + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => array( + 'field_multifield_w_text_fields_field_text_sum_filtered_format', + ), + 'field_multifield_w_text_fields_id' => array( + 'field_multifield_w_text_fields_id', + ), + ), + 'mysql_character_set' => 'utf8', +)); + +$connection->insert('field_revision_field_multifield_w_text_fields') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_multifield_w_text_fields_field_text_plain_value', + 'field_multifield_w_text_fields_field_text_plain_format', + 'field_multifield_w_text_fields_field_text_sum_filtered_value', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary', + 'field_multifield_w_text_fields_field_text_sum_filtered_format', + 'field_multifield_w_text_fields_id', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '119', + 'language' => 'en', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Content with multifields text plain copy [delta0] [rev1]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => 'Content with multifields text summary copy [delta0] [rev1]', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '2', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '119', + 'language' => 'en', + 'delta' => '1', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Content with multifields text plain copy [delta1] [rev1]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => 'Content with multifields text summary copy [delta1] [rev1]', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '3', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '120', + 'language' => 'en', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Content with multifields text plain copy [delta0] [rev2]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => 'Content with multifields text summary copy [delta0] [rev2]', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '2', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'deleted' => '0', + 'entity_id' => '112', + 'revision_id' => '120', + 'language' => 'en', + 'delta' => '1', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Content with multifields text plain copy [delta1] [rev2]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => 'Content with multifields text summary copy [delta1] [rev2]', + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '3', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Multifield term "text plain" copy [FR]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => "Multifield term \"text summary filtered\" summary [FR]\r\n\r\nMultifield term \"text summary filtered\" copy [FR]", + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '1', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'delta' => '0', + 'field_multifield_w_text_fields_field_text_plain_value' => 'Multifield term "text plain" copy [IS - default]', + 'field_multifield_w_text_fields_field_text_plain_format' => NULL, + 'field_multifield_w_text_fields_field_text_sum_filtered_value' => "Multifield term \"text summary filtered\" summary [IS - default]\r\n\r\nMultifield term \"text summary filtered\" copy [IS - default]", + 'field_multifield_w_text_fields_field_text_sum_filtered_summary' => '', + 'field_multifield_w_text_fields_field_text_sum_filtered_format' => 'filtered_html', + 'field_multifield_w_text_fields_id' => '1', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_name_field.php b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_name_field.php new file mode 100644 index 0000000..0445f4d --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/field_revision_name_field.php @@ -0,0 +1,48 @@ +insert('field_revision_name_field') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'name_field_value', + 'name_field_format', +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'fr', + 'delta' => '0', + 'name_field_value' => 'Multifield term [FR]', + 'name_field_format' => NULL, +)) +->values(array( + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'deleted' => '0', + 'entity_id' => '126', + 'revision_id' => '126', + 'language' => 'is', + 'delta' => '0', + 'name_field_value' => 'Multifield term [IS - default]', + 'name_field_format' => NULL, +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/multifield.php b/tests/fixtures/drupal7_multifield_on_core_fixture/multifield.php new file mode 100644 index 0000000..f9587c1 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/multifield.php @@ -0,0 +1,49 @@ +schema()->createTable('multifield', array( + 'fields' => array( + 'mfid' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'machine_name' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'label' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '255', + 'default' => '', + ), + 'description' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array( + 'mfid', + ), + 'unique keys' => array( + 'machine_name' => array( + 'machine_name', + ), + ), + 'mysql_character_set' => 'utf8', +)); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/node.php b/tests/fixtures/drupal7_multifield_on_core_fixture/node.php new file mode 100644 index 0000000..b1b1461 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/node.php @@ -0,0 +1,47 @@ +insert('node') +->fields(array( + 'nid', + 'vid', + 'type', + 'language', + 'title', + 'uid', + 'status', + 'created', + 'changed', + 'comment', + 'promote', + 'sticky', + 'tnid', + 'translate', +)) +->values(array( + 'nid' => '112', + 'vid' => '120', + 'type' => 'type_with_multifields', + 'language' => 'en', + 'title' => 'Content with multifields [rev2]', + 'uid' => '3', + 'status' => '1', + 'created' => '1622704732', + 'changed' => '1622704795', + 'comment' => '1', + 'promote' => '1', + 'sticky' => '0', + 'tnid' => '0', + 'translate' => '0', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/node_comment_statistics.php b/tests/fixtures/drupal7_multifield_on_core_fixture/node_comment_statistics.php new file mode 100644 index 0000000..8b2443c --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/node_comment_statistics.php @@ -0,0 +1,31 @@ +insert('node_comment_statistics') +->fields(array( + 'nid', + 'cid', + 'last_comment_timestamp', + 'last_comment_name', + 'last_comment_uid', + 'comment_count', +)) +->values(array( + 'nid' => '112', + 'cid' => '0', + 'last_comment_timestamp' => '1622704732', + 'last_comment_name' => NULL, + 'last_comment_uid' => '3', + 'comment_count' => '0', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/node_counter.php b/tests/fixtures/drupal7_multifield_on_core_fixture/node_counter.php new file mode 100644 index 0000000..6ae0b4d --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/node_counter.php @@ -0,0 +1,27 @@ +insert('node_counter') +->fields(array( + 'nid', + 'totalcount', + 'daycount', + 'timestamp', +)) +->values(array( + 'nid' => '112', + 'totalcount' => '1', + 'daycount' => '0', + 'timestamp' => '1622705408', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/node_revision.php b/tests/fixtures/drupal7_multifield_on_core_fixture/node_revision.php new file mode 100644 index 0000000..5a40757 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/node_revision.php @@ -0,0 +1,51 @@ +insert('node_revision') +->fields(array( + 'nid', + 'vid', + 'uid', + 'title', + 'log', + 'timestamp', + 'status', + 'comment', + 'promote', + 'sticky', +)) +->values(array( + 'nid' => '112', + 'vid' => '119', + 'uid' => '3', + 'title' => 'Content with multifields [rev1]', + 'log' => 'Initial revision.', + 'timestamp' => '1622704732', + 'status' => '1', + 'comment' => '1', + 'promote' => '1', + 'sticky' => '0', +)) +->values(array( + 'nid' => '112', + 'vid' => '120', + 'uid' => '3', + 'title' => 'Content with multifields [rev2]', + 'log' => 'New revision.', + 'timestamp' => '1622704795', + 'status' => '1', + 'comment' => '1', + 'promote' => '1', + 'sticky' => '0', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/node_type.php b/tests/fixtures/drupal7_multifield_on_core_fixture/node_type.php new file mode 100644 index 0000000..6697745 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/node_type.php @@ -0,0 +1,45 @@ +insert('node_type') +->fields(array( + 'type', + 'name', + 'base', + 'module', + 'description', + 'help', + 'has_title', + 'title_label', + 'custom', + 'modified', + 'locked', + 'disabled', + 'orig_type', +)) +->values(array( + 'type' => 'type_with_multifields', + 'name' => 'Type with multifields', + 'base' => 'node_content', + 'module' => 'node', + 'description' => 'A content type for testing the migration of multifield values.', + 'help' => '', + 'has_title' => '1', + 'title_label' => 'Title', + 'custom' => '1', + 'modified' => '1', + 'locked' => '0', + 'disabled' => '0', + 'orig_type' => 'type_with_multifields', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/role_permission.php b/tests/fixtures/drupal7_multifield_on_core_fixture/role_permission.php new file mode 100644 index 0000000..6d38aab --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/role_permission.php @@ -0,0 +1,25 @@ +insert('role_permission') +->fields(array( + 'rid', + 'permission', + 'module', +)) +->values(array( + 'rid' => '3', + 'permission' => 'administer multifield', + 'module' => 'multifield', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/system.php b/tests/fixtures/drupal7_multifield_on_core_fixture/system.php new file mode 100644 index 0000000..d085699 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/system.php @@ -0,0 +1,37 @@ +insert('system') +->fields(array( + 'filename', + 'name', + 'type', + 'owner', + 'status', + 'bootstrap', + 'schema_version', + 'weight', + 'info', +)) +->values(array( + 'filename' => 'sites/all/modules/multifield/multifield.module', + 'name' => 'multifield', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'bootstrap' => '0', + 'schema_version' => '7101', + 'weight' => '0', + 'info' => 'a:16:{s:4:"name";s:10:"Multifield";s:11:"description";s:28:"Provides a combo field type.";s:7:"package";s:6:"Fields";s:4:"core";s:3:"7.x";s:12:"dependencies";a:2:{i:0;s:6:"ctools";i:1;s:5:"field";}s:9:"configure";s:26:"admin/structure/multifield";s:5:"files";a:10:{i:0;s:30:"MultifieldEntityController.php";i:1;s:43:"tests/MultifieldAdministrationTestCase.test";i:2;s:44:"tests/MultifieldCommerceIntegrationTest.test";i:3;s:42:"tests/MultifieldDevelGenerateTestCase.test";i:4;s:46:"tests/MultifieldEntityTranslationTestCase.test";i:5;s:38:"tests/MultifieldFileUsageTestCase.test";i:6;s:45:"tests/MultifieldNodeCloneIntegrationTest.test";i:7;s:45:"tests/MultifieldReplicateIntegrationTest.test";i:8;s:29:"tests/MultifieldTestBase.test";i:9;s:33:"tests/MultifieldUnitTestCase.test";}s:17:"test_dependencies";a:11:{i:0;s:12:"addressfield";i:1;s:5:"clone";i:2;s:8:"commerce";i:3;s:4:"date";i:4;s:5:"devel";i:5;s:5:"email";i:6;s:15:"entityreference";i:7;s:18:"entity_translation";i:8;s:16:"field_collection";i:9;s:9:"replicate";i:10;s:3:"url";}s:7:"version";s:14:"7.x-1.0-alpha4";s:7:"project";s:10:"multifield";s:9:"datestamp";s:10:"1466105019";s:5:"mtime";i:1466105019;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;s:8:"required";b:1;s:11:"explanation";s:73:"Field type(s) in use - see Field list";}', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_term_data.php b/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_term_data.php new file mode 100644 index 0000000..6f189cc --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_term_data.php @@ -0,0 +1,35 @@ +insert('taxonomy_term_data') +->fields(array( + 'tid', + 'vid', + 'name', + 'description', + 'format', + 'weight', + 'language', + 'i18n_tsid', +)) +->values(array( + 'tid' => '126', + 'vid' => '109', + 'name' => 'Multifield term [IS - default]', + 'description' => 'Multifield term description [IS - default]', + 'format' => 'filtered_html', + 'weight' => '0', + 'language' => 'is', + 'i18n_tsid' => '0', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_term_hierarchy.php b/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_term_hierarchy.php new file mode 100644 index 0000000..ed5f23d --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_term_hierarchy.php @@ -0,0 +1,23 @@ +insert('taxonomy_term_hierarchy') +->fields(array( + 'tid', + 'parent', +)) +->values(array( + 'tid' => '126', + 'parent' => '0', +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_vocabulary.php b/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_vocabulary.php new file mode 100644 index 0000000..4ef3a1d --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/taxonomy_vocabulary.php @@ -0,0 +1,37 @@ +insert('taxonomy_vocabulary') +->fields(array( + 'vid', + 'name', + 'machine_name', + 'description', + 'hierarchy', + 'module', + 'weight', + 'language', + 'i18n_mode', +)) +->values(array( + 'vid' => '109', + 'name' => 'Vocabulary with multifields', + 'machine_name' => 'vocabulary_with_multifields', + 'description' => 'A taxonomy vocabulary for testing the migration of multifield values.', + 'hierarchy' => '0', + 'module' => 'taxonomy', + 'weight' => '0', + 'language' => 'und', + 'i18n_mode' => '2', // With up-to-date I18n, this was set to '32768'. +)) +->execute(); diff --git a/tests/fixtures/drupal7_multifield_on_core_fixture/variable.php b/tests/fixtures/drupal7_multifield_on_core_fixture/variable.php new file mode 100644 index 0000000..741bc32 --- /dev/null +++ b/tests/fixtures/drupal7_multifield_on_core_fixture/variable.php @@ -0,0 +1,136 @@ +select('variable', 'v') + ->fields('v', ['value']) + ->condition('v.name', 'entityreference:base-tables') + ->execute() + ->fetchCol()[0]; +$value = unserialize($value_serialized); +assert(is_array($value)); +$value['multifield'] = [ + 'multifield', + 'id', +]; +$connection->update('variable') + ->condition('name', 'entityreference:base-tables') + ->fields(['value' => serialize($value)]) + ->execute(); + +$connection->insert('variable') +->fields(array( + 'name', + 'value', +)) +->values(array( + 'name' => 'additional_settings__active_tab_type_with_multifields', + 'value' => 's:13:"edit-workflow";', +)) +->values(array( + 'name' => 'comment_anonymous_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'comment_default_mode_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'comment_default_per_page_type_with_multifields', + 'value' => 's:2:"50";', +)) +->values(array( + 'name' => 'comment_form_location_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'comment_preview_type_with_multifields', + 'value' => 's:1:"0";', +)) +->values(array( + 'name' => 'comment_subject_field_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'comment_type_with_multifields', + 'value' => 's:1:"0";', +)) +->values(array( + 'name' => 'entity_translation_comment_filter_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'entity_translation_hide_translation_links_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'entity_translation_node_metadata_type_with_multifields', + 'value' => 's:1:"0";', +)) +->values(array( + 'name' => 'entity_translation_settings_taxonomy_term__vocabulary_with_multifields', + 'value' => 'a:5:{s:16:"default_language";s:13:"xx-et-default";s:22:"hide_language_selector";i:0;s:21:"exclude_language_none";i:0;s:13:"lock_language";i:1;s:27:"shared_fields_original_only";i:1;}', +)) +->values(array( + 'name' => 'field_bundle_settings_multifield__field_multifield_complex_fields', + 'value' => 'a:2:{s:10:"view_modes";a:0:{}s:12:"extra_fields";a:2:{s:4:"form";a:0:{}s:7:"display";a:0:{}}}', +)) +->values(array( + 'name' => 'field_bundle_settings_multifield__field_multifield_w_text_fields', + 'value' => 'a:2:{s:10:"view_modes";a:0:{}s:12:"extra_fields";a:2:{s:4:"form";a:0:{}s:7:"display";a:0:{}}}', +)) +->values(array( + 'name' => 'field_bundle_settings_node__type_with_multifields', + 'value' => 'a:2:{s:10:"view_modes";a:0:{}s:12:"extra_fields";a:2:{s:4:"form";a:1:{s:5:"title";a:1:{s:6:"weight";s:2:"-5";}}s:7:"display";a:0:{}}}', +)) +->values(array( + 'name' => 'field_bundle_settings_taxonomy_term__vocabulary_with_multifields', + 'value' => 'a:2:{s:10:"view_modes";a:0:{}s:12:"extra_fields";a:2:{s:4:"form";a:1:{s:8:"language";a:1:{s:6:"weight";s:1:"5";}}s:7:"display";a:0:{}}}', +)) +->values(array( + 'name' => 'i18n_sync_node_type_type_with_multifields', + 'value' => 'a:0:{}', +)) +->values(array( + 'name' => 'language_content_type_type_with_multifields', + 'value' => 's:1:"0";', +)) +->values(array( + 'name' => 'menu_options_type_with_multifields', + 'value' => 'a:0:{}', +)) +->values(array( + 'name' => 'menu_parent_type_with_multifields', + 'value' => 's:11:"main-menu:0";', +)) +->values(array( + 'name' => 'multifield_max_id', + 'value' => 's:1:"4";', +)) +->values(array( + 'name' => 'node_options_type_with_multifields', + 'value' => 'a:3:{i:0;s:6:"status";i:1;s:7:"promote";i:2;s:8:"revision";}', +)) +->values(array( + 'name' => 'node_preview_type_with_multifields', + 'value' => 's:1:"0";', +)) +->values(array( + 'name' => 'node_submitted_type_with_multifields', + 'value' => 'i:0;', +)) +->values(array( + 'name' => 'save_continue_type_with_multifields', + 'value' => 's:19:"Save and add fields";', +)) +->execute(); diff --git a/tests/src/Kernel/migrate/MultifieldTest.php b/tests/src/Kernel/migrate/MultifieldTest.php new file mode 100644 index 0000000..0dc1bd7 --- /dev/null +++ b/tests/src/Kernel/migrate/MultifieldTest.php @@ -0,0 +1,193 @@ +installEntitySchema('file'); + $this->installEntitySchema('node'); + $this->installEntitySchema('paragraph'); + $this->installEntitySchema('comment'); + $this->installSchema('node', ['node_access']); + $this->installSchema('comment', [ + 'comment_entity_statistics', + ]); + $this->installSchema('file', ['file_usage']); + $this->installConfig(['comment', 'node']); + + $this->loadFixture(implode(DIRECTORY_SEPARATOR, [ + drupal_get_path('module', 'migrate_drupal'), + 'tests', + 'fixtures', + 'drupal7.php', + ])); + + $this->loadFixture(implode(DIRECTORY_SEPARATOR, [ + drupal_get_path('module', 'paragraphs'), + 'tests', + 'fixtures', + 'drupal7_multifield_on_core_fixture.php', + ])); + } + + /** + * Tests the migration of a content with paragraphs and field collections. + */ + public function testMultifieldMigration() { + // The Drupal 8 entity revision migration causes a file not found exception + // without properly migrated files. For this test, it is enough to properly + // migrate the public files. + $fs_fixture_path = implode(DIRECTORY_SEPARATOR, [ + DRUPAL_ROOT, + drupal_get_path('module', 'paragraphs'), + 'tests', + 'fixtures', + ]); + $file_migration = $this->getMigration('d7_file'); + $source = $file_migration->getSourceConfiguration(); + $source['constants']['source_base_path'] = $fs_fixture_path; + $file_migration->set('source', $source); + + // Ignore migration messages of core migrations we don't touched, or which + // are saving migration messages but aren't crucial. + $this->startCollectingMessages(); + $this->executeMigration($file_migration); + $this->executeMigrations([ + 'language', + 'default_language', + 'd7_user_role', + 'd7_node_type', + 'd7_comment_type', + 'taxonomy_settings', + 'd7_taxonomy_vocabulary', + 'd7_field', + 'd7_view_modes', + 'd7_entity_translation_settings', + ]); + + // Migrate paragraph types from multifield bundles. + $this->startCollectingMessages(); + $this->executeMigrations([ + 'multifield_type', + 'multifield_translation_settings', + ]); + $this->assertEquals([], $this->migrateMessages); + + // Migrate field instances. + $this->executeMigrations([ + 'd7_field_instance', + 'd7_field_formatter_settings', + ]); + + $this->startCollectingMessages(); + $this->executeMigrations([ + 'multifield', + ]); + $this->assertEquals([], $this->migrateMessages); + + $this->assertEquals(8, $this->getActualParagraphRevisionTranslationsCount()); + $this->assertEquals(7, $this->getActualParagraphRevisionsCount()); + $this->assertEquals(4, $this->getActualParagraphsCount()); + + // Execute host content entity migrations. + $this->executeMigrations([ + 'd7_user', + ]); + + $this->startCollectingMessages(); + $this->executeMigrations([ + 'd7_taxonomy_term', + 'd7_taxonomy_term_entity_translation:vocabulary_with_multifields', + 'd7_node_complete:type_with_multifields', + ]); + $this->assertEquals([], $this->migrateMessages); + + $this->assertEquals(8, $this->getActualParagraphRevisionTranslationsCount()); + $this->assertEquals(7, $this->getActualParagraphRevisionsCount()); + $this->assertEquals(4, $this->getActualParagraphsCount()); + + $this->assertMultifieldTextType(); + $this->assertMultifieldComplexType(); + + $this->assertTermMultifieldFieldStorage(); + $this->assertTermMultifieldFieldInstance(); + $this->assertTerm126(); + + $this->assertNodeMultifieldTextFieldStorage(); + $this->assertNodeMultifieldComplexFieldStorage(); + $this->assertNodeMultifieldTextFieldInstance(); + $this->assertNodeMultifieldComplexFieldInstance(); + $this->assertNode112(); + } + + /** + * Asserts whether no migration errors were logged to $this->migrateMessages. + * + * This is a DX helper method which makes migration messages easy to capture + * when something goes wrong. + */ + protected function assertNoErrors() { + foreach ($this->migrateMessages as $severity => $messages) { + $actual[$severity] = array_reduce( + $messages, + function (array $carry, $message) { + $carry[] = (string) $message; + return $carry; + }, + [] + ); + $dummy[$severity] = array_fill(0, count($messages), '...'); + } + $this->assertEquals($actual ?? [], $dummy ?? []); + } + +} diff --git a/tests/src/Traits/MultifieldMigrationsTrait.php b/tests/src/Traits/MultifieldMigrationsTrait.php new file mode 100644 index 0000000..9b78e92 --- /dev/null +++ b/tests/src/Traits/MultifieldMigrationsTrait.php @@ -0,0 +1,703 @@ +getStorage('paragraphs_type'); + assert($storage instanceof ConfigEntityStorageInterface); + + $type = $storage->loadOverrideFree('field_multifield_w_text_fields'); + + $this->assertEquals([ + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [], + 'id' => 'field_multifield_w_text_fields', + 'label' => 'Multifield with text fields', + 'icon_uuid' => NULL, + 'icon_default' => NULL, + 'description' => '', + 'behavior_plugins' => [], + ], $this->getEntityValues($type)); + } + + /** + * Tests the paragraphs type migrated from "field_multifield_complex_fields". + */ + protected function assertMultifieldComplexType() { + $storage = \Drupal::entityTypeManager()->getStorage('paragraphs_type'); + assert($storage instanceof ConfigEntityStorageInterface); + + $type = $storage->loadOverrideFree('field_multifield_complex_fields'); + + $this->assertEquals([ + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [], + 'id' => 'field_multifield_complex_fields', + 'label' => 'Multifield with complex fields', + 'icon_uuid' => NULL, + 'icon_default' => NULL, + 'description' => '', + 'behavior_plugins' => [], + ], $this->getEntityValues($type)); + } + + /** + * Tests field storage migrated for "field_multifield_w_text_fields" in nodes. + */ + protected function assertNodeMultifieldTextFieldStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('field_storage_config'); + assert($storage instanceof FieldStorageConfigStorage); + + $field_storage = $storage->loadOverrideFree('node.field_multifield_w_text_fields'); + + $this->assertEquals([ + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'status' => TRUE, + 'dependencies' => [ + 'module' => [ + 'entity_reference_revisions', + 'node', + 'paragraphs', + ], + ], + 'id' => 'node.field_multifield_w_text_fields', + 'field_name' => 'field_multifield_w_text_fields', + 'entity_type' => 'node', + 'type' => 'entity_reference_revisions', + 'settings' => [ + 'target_type' => 'paragraph', + ], + 'module' => 'entity_reference_revisions', + 'locked' => FALSE, + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'translatable' => TRUE, + 'indexes' => [], + 'custom_storage' => FALSE, + ], $this->getEntityValues($field_storage)); + } + + /** + * Tests field storage migrated for "field_multifield_complex_fields" (nodes). + */ + protected function assertNodeMultifieldComplexFieldStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('field_storage_config'); + assert($storage instanceof FieldStorageConfigStorage); + + $field_storage = $storage->loadOverrideFree('node.field_multifield_complex_fields'); + + $this->assertEquals([ + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'status' => TRUE, + 'dependencies' => [ + 'module' => [ + 'entity_reference_revisions', + 'node', + 'paragraphs', + ], + ], + 'id' => 'node.field_multifield_complex_fields', + 'field_name' => 'field_multifield_complex_fields', + 'entity_type' => 'node', + 'type' => 'entity_reference_revisions', + 'settings' => [ + 'target_type' => 'paragraph', + ], + 'module' => 'entity_reference_revisions', + 'locked' => FALSE, + 'cardinality' => 1, + 'translatable' => TRUE, + 'indexes' => [], + 'custom_storage' => FALSE, + ], $this->getEntityValues($field_storage)); + } + + /** + * Tests the field storage migrated from the taxonomy multifield field. + */ + protected function assertTermMultifieldFieldStorage() { + $storage = \Drupal::entityTypeManager()->getStorage('field_storage_config'); + assert($storage instanceof FieldStorageConfigStorage); + + $field_storage = $storage->loadOverrideFree('taxonomy_term.field_multifield_w_text_fields'); + + $this->assertEquals([ + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'status' => TRUE, + 'dependencies' => [ + 'module' => [ + 'entity_reference_revisions', + 'paragraphs', + 'taxonomy', + ], + ], + 'id' => 'taxonomy_term.field_multifield_w_text_fields', + 'field_name' => 'field_multifield_w_text_fields', + 'entity_type' => 'taxonomy_term', + 'type' => 'entity_reference_revisions', + 'settings' => [ + 'target_type' => 'paragraph', + ], + 'module' => 'entity_reference_revisions', + 'locked' => FALSE, + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'translatable' => TRUE, + 'indexes' => [], + 'custom_storage' => FALSE, + ], $this->getEntityValues($field_storage)); + } + + /** + * Tests the field migrated for "field_multifield_w_text_fields" in the node. + */ + protected function assertNodeMultifieldTextFieldInstance() { + $storage = \Drupal::entityTypeManager()->getStorage('field_config'); + assert($storage instanceof FieldConfigStorage); + + $field_instance = $storage->loadOverrideFree('node.type_with_multifields.field_multifield_w_text_fields'); + + $this->assertEquals([ + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [ + 'config' => [ + 'field.storage.node.field_multifield_w_text_fields', + 'node.type.type_with_multifields', + 'paragraphs.paragraphs_type.field_multifield_w_text_fields', + ], + 'module' => [ + 'entity_reference_revisions', + ], + ], + 'id' => 'node.type_with_multifields.field_multifield_w_text_fields', + 'field_name' => 'field_multifield_w_text_fields', + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'label' => 'Multifield with text fields', + 'description' => '', + 'required' => FALSE, + 'translatable' => FALSE, + 'default_value' => [], + 'default_value_callback' => '', + 'settings' => [ + 'handler' => 'default:paragraph', + 'handler_settings' => [ + 'negate' => 0, + 'target_bundles' => [ + 'field_multifield_w_text_fields' => 'field_multifield_w_text_fields', + ], + ], + ], + 'field_type' => 'entity_reference_revisions', + ], $this->getEntityValues($field_instance)); + } + + /** + * Tests the field migrated for "field_multifield_complex_fields" in the node. + */ + protected function assertNodeMultifieldComplexFieldInstance() { + $storage = \Drupal::entityTypeManager()->getStorage('field_config'); + assert($storage instanceof FieldConfigStorage); + + $field_instance = $storage->loadOverrideFree('node.type_with_multifields.field_multifield_complex_fields'); + + $this->assertEquals([ + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [ + 'config' => [ + 'field.storage.node.field_multifield_complex_fields', + 'node.type.type_with_multifields', + 'paragraphs.paragraphs_type.field_multifield_complex_fields', + ], + 'module' => [ + 'entity_reference_revisions', + ], + ], + 'id' => 'node.type_with_multifields.field_multifield_complex_fields', + 'field_name' => 'field_multifield_complex_fields', + 'entity_type' => 'node', + 'bundle' => 'type_with_multifields', + 'label' => 'Multifield with complex fields', + 'description' => '', + 'required' => FALSE, + 'translatable' => FALSE, + 'default_value' => [], + 'default_value_callback' => '', + 'settings' => [ + 'handler' => 'default:paragraph', + 'handler_settings' => [ + 'negate' => 0, + 'target_bundles' => [ + 'field_multifield_complex_fields' => 'field_multifield_complex_fields', + ], + ], + ], + 'field_type' => 'entity_reference_revisions', + ], $this->getEntityValues($field_instance)); + } + + /** + * Tests the field instance migrated from the taxonomy multifield. + */ + protected function assertTermMultifieldFieldInstance() { + $storage = \Drupal::entityTypeManager()->getStorage('field_config'); + assert($storage instanceof FieldConfigStorage); + + $field_instance = $storage->loadOverrideFree('taxonomy_term.vocabulary_with_multifields.field_multifield_w_text_fields'); + + $this->assertEquals([ + 'langcode' => 'en', + 'status' => TRUE, + 'dependencies' => [ + 'config' => [ + 'field.storage.taxonomy_term.field_multifield_w_text_fields', + 'paragraphs.paragraphs_type.field_multifield_w_text_fields', + 'taxonomy.vocabulary.vocabulary_with_multifields', + ], + 'module' => [ + 'entity_reference_revisions', + ], + ], + 'id' => 'taxonomy_term.vocabulary_with_multifields.field_multifield_w_text_fields', + 'field_name' => 'field_multifield_w_text_fields', + 'entity_type' => 'taxonomy_term', + 'bundle' => 'vocabulary_with_multifields', + 'label' => 'Multifield with text fields', + 'description' => '', + 'required' => FALSE, + 'translatable' => TRUE, + 'default_value' => [], + 'default_value_callback' => '', + 'settings' => [ + 'handler' => 'default:paragraph', + 'handler_settings' => [ + 'negate' => 0, + 'target_bundles' => [ + 'field_multifield_w_text_fields' => 'field_multifield_w_text_fields', + ], + ], + ], + 'field_type' => 'entity_reference_revisions', + ], $this->getEntityValues($field_instance)); + } + + /** + * Tests the values of the term migrated form D7 with tid 126. + */ + protected function assertTerm126() { + $storage = $this->container->get('entity_type.manager')->getStorage('taxonomy_term'); + assert($storage instanceof EntityStorageInterface); + $term = $storage->load(126); + $this->assertInstanceOf(Term::class, $term); + + // Check the default (IS) translation. + $this->assertEquals([ + 'tid' => [['value' => '126']], + 'langcode' => [['value' => 'is']], + 'vid' => [['target_id' => 'vocabulary_with_multifields']], + 'revision_user' => [], + 'revision_log_message' => [], + 'status' => [['value' => '1']], + 'name' => [['value' => 'Multifield term [IS - default]']], + 'description' => [ + [ + 'value' => 'Multifield term description [IS - default]', + 'format' => 'filtered_html', + ], + ], + 'weight' => [['value' => '0']], + 'parent' => [['target_id' => '0']], + 'default_langcode' => [['value' => '1']], + 'revision_default' => [['value' => '1']], + 'revision_translation_affected' => [['value' => '1']], + 'content_translation_source' => [['value' => 'und']], + 'content_translation_outdated' => [['value' => '0']], + 'field_multifield_w_text_fields' => [ + 0 => [ + 'langcode' => [['value' => 'is']], + 'type' => [['target_id' => 'field_multifield_w_text_fields']], + 'status' => [['value' => '1']], + 'parent_id' => [['value' => '126']], + 'parent_type' => [['value' => 'taxonomy_term']], + 'parent_field_name' => [ + [ + 'value' => 'field_multifield_w_text_fields', + ], + ], + 'behavior_settings' => [['value' => 'a:0:{}']], + 'default_langcode' => [['value' => '1']], + 'revision_default' => [['value' => '1']], + 'revision_translation_affected' => [['value' => '1']], + 'content_translation_source' => [['value' => 'und']], + 'content_translation_outdated' => [['value' => '0']], + 'field_text_plain' => [ + [ + 'value' => 'Multifield term "text plain" copy [IS - default]', + ], + ], + 'field_text_sum_filtered' => [ + [ + 'value' => "Multifield term \"text summary filtered\" summary [IS - default]\r\n\r\nMultifield term \"text summary filtered\" copy [IS - default]", + 'summary' => '', + 'format' => 'filtered_html', + ], + ], + ], + ], + ], $this->getEntityValues($term)); + + // Check the French translation. + $this->assertEquals([ + 'tid' => [['value' => '126']], + 'langcode' => [['value' => 'fr']], + 'vid' => [['target_id' => 'vocabulary_with_multifields']], + 'revision_user' => [], + 'revision_log_message' => [], + 'status' => [['value' => '1']], + 'name' => [['value' => 'Multifield term [FR]']], + 'description' => [ + [ + 'value' => 'Multifield term description [FR]', + 'format' => 'filtered_html', + ], + ], + 'weight' => [['value' => '0']], + 'parent' => [['target_id' => '0']], + 'default_langcode' => [['value' => '0']], + 'revision_default' => [['value' => '1']], + 'revision_translation_affected' => [['value' => '1']], + 'content_translation_source' => [['value' => 'is']], + 'content_translation_outdated' => [['value' => '0']], + 'field_multifield_w_text_fields' => [ + 0 => [ + 'langcode' => [['value' => 'fr']], + 'type' => [['target_id' => 'field_multifield_w_text_fields']], + 'status' => [['value' => '1']], + 'parent_id' => [['value' => '126']], + 'parent_type' => [['value' => 'taxonomy_term']], + 'parent_field_name' => [ + [ + 'value' => 'field_multifield_w_text_fields', + ], + ], + 'behavior_settings' => [['value' => 'a:0:{}']], + 'default_langcode' => [['value' => '0']], + 'revision_default' => [['value' => '1']], + 'revision_translation_affected' => [['value' => '1']], + 'content_translation_source' => [['value' => 'is']], + 'content_translation_outdated' => [['value' => '0']], + 'field_text_plain' => [ + [ + 'value' => 'Multifield term "text plain" copy [FR]', + ], + ], + 'field_text_sum_filtered' => [ + [ + 'value' => "Multifield term \"text summary filtered\" summary [FR]\r\n\r\nMultifield term \"text summary filtered\" copy [FR]", + 'summary' => '', + 'format' => 'filtered_html', + ], + ], + ], + ], + ], $this->getEntityValues($term, 'fr')); + } + + /** + * Tests the values of the node migrated form D7 nid 112. + */ + protected function assertNode112() { + $storage = $this->container->get('entity_type.manager')->getStorage('node'); + assert($storage instanceof EntityStorageInterface); + $node = $storage->loadRevision(119); + $this->assertInstanceOf(Node::class, $node); + + // Check the first (obsolete) revision. + $this->assertEquals([ + 'nid' => [['value' => '112']], + 'vid' => [['value' => '119']], + 'langcode' => [['value' => 'en']], + 'type' => [['target_id' => 'type_with_multifields']], + 'revision_timestamp' => [['value' => '1622704732']], + 'revision_uid' => [['target_id' => '3']], + 'revision_log' => [['value' => 'Initial revision.']], + 'status' => [['value' => 1]], + 'uid' => [['target_id' => '3']], + 'title' => [['value' => 'Content with multifields [rev1]']], + 'created' => [['value' => '1622704732']], + 'changed' => [['value' => '1622704732']], + 'promote' => [['value' => 1]], + 'sticky' => [['value' => 0]], + 'default_langcode' => [['value' => 1]], + 'revision_default' => [['value' => 1]], + 'revision_translation_affected' => [['value' => 1]], + 'content_translation_source' => [], + 'content_translation_outdated' => [['value' => 0]], + 'body' => [], + 'field_multifield_w_text_fields' => [ + 0 => [ + 'langcode' => [['value' => 'en']], + 'type' => [['target_id' => 'field_multifield_w_text_fields']], + 'status' => [['value' => 1]], + 'parent_id' => [['value' => 112]], + 'parent_type' => [['value' => 'node']], + 'parent_field_name' => [ + ['value' => 'field_multifield_w_text_fields'], + ], + 'behavior_settings' => [['value' => 'a:0:{}']], + 'default_langcode' => [['value' => 1]], + 'revision_default' => [['value' => 1]], + 'revision_translation_affected' => [['value' => 1]], + 'content_translation_source' => [['value' => 'und']], + 'content_translation_outdated' => [['value' => 0]], + 'field_text_plain' => [ + 0 => [ + 'value' => 'Content with multifields text plain copy [delta0] [rev1]', + ] + ], + 'field_text_sum_filtered' => [ + 0 => [ + 'value' => 'Content with multifields text summary copy [delta0] [rev1]', + 'summary' => '', + 'format' => 'filtered_html', + ] + ], + ], + 1 => [ + 'langcode' => [['value' => 'en']], + 'type' => [['target_id' => 'field_multifield_w_text_fields']], + 'status' => [['value' => 1]], + 'parent_id' => [['value' => 112]], + 'parent_type' => [['value' => 'node']], + 'parent_field_name' => [ + ['value' => 'field_multifield_w_text_fields'], + ], + 'behavior_settings' => [['value' => 'a:0:{}']], + 'default_langcode' => [['value' => 1]], + 'revision_default' => [['value' => 1]], + 'revision_translation_affected' => [['value' => 1]], + 'content_translation_source' => [['value' => 'und']], + 'content_translation_outdated' => [['value' => 0]], + 'field_text_plain' => [ + 0 => [ + 'value' => 'Content with multifields text plain copy [delta1] [rev1]', + ] + ], + 'field_text_sum_filtered' => [ + 0 => [ + 'value' => 'Content with multifields text summary copy [delta1] [rev1]', + 'summary' => '', + 'format' => 'filtered_html', + ] + ], + ], + ], + 'field_multifield_complex_fields' => [ + 0 => [ + 'langcode' => [['value' => 'en']], + 'type' => [['target_id' => 'field_multifield_complex_fields']], + 'status' => [['value' => 1]], + 'parent_id' => [['value' => 112]], + 'parent_type' => [['value' => 'node']], + 'parent_field_name' => [ + ['value' => 'field_multifield_complex_fields'] + ], + 'behavior_settings' => [['value' => 'a:0:{}']], + 'default_langcode' => [['value' => 1]], + 'revision_default' => [['value' => 1]], + 'revision_translation_affected' => [['value' => 1]], + 'content_translation_source' => [['value' => 'und']], + 'content_translation_outdated' => [['value' => 0]], + 'field_tags' => [ + 0 => [ + 'target_id' => '11', + ], + ], + 'field_date' => [ + 0 => [ + 'value' => '2021-06-03T07:15:00', + ], + ], + 'field_link' => [ + 0 => [ + 'uri' => 'https://www.drupal.org', + 'title' => 'Drupal org [rev1]', + 'options' => [ + 'attributes' => [], + ], + ], + ], + ], + ], + ], $this->getEntityValues($node)); + } + + /** + * Gets the values of the paragraphs referenced by the given entity. + * + * @param \Drupal\Core\Entity\EntityInterface $host_entity + * The entity. + * @param string|null $langcode + * The language code. + * + * @return array[] + * The values of the paragraphs referenced by the given entity. + */ + protected function getParagraphFieldValues(EntityInterface $host_entity, $langcode = NULL) :array { + if (!$host_entity instanceof ContentEntityInterface) { + return []; + } + return array_reduce($host_entity->getFields(FALSE), function (array $carry, FieldItemListInterface $field) use ($langcode) { + if ($field instanceof EntityReferenceRevisionsFieldItemList) { + $carry[$field->getName()] = array_reduce($field->referencedEntities(), function (array $value, EntityInterface $paragraph) use ($langcode) { + $paragraph = $paragraph->hasTranslation($langcode) + ? $paragraph->getTranslation($langcode) + : $paragraph->getUntranslated(); + $value[] = array_diff_key($paragraph->toArray(), [ + 'id' => TRUE, + 'revision_id' => TRUE, + 'uuid' => TRUE, + 'created' => TRUE, + 'content_translation_changed' => TRUE, + 'path' => TRUE, + ]); + return $value; + }, []); + } + return $carry; + }, []); + } + + /** + * Returns the entity properties as an array. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * @param string|null $langcode + * The language code. + * + * @return array[] + * The entity properties as an array. + */ + protected function getEntityValues(EntityInterface $entity, $langcode = NULL) :array { + if ($entity instanceof TranslatableInterface) { + $entity = $langcode + ? $entity->getTranslation($langcode) + : $entity->getUntranslated(); + } + + $keys_to_ignore = [ + 'uuid', + 'path', + 'persist_with_no_fields', + ]; + + // In D7, only nodes are revisionable. + if (!$entity instanceof NodeInterface) { + $keys_to_ignore = array_merge( + $keys_to_ignore, + [ + 'revision_id', + 'revision_created', + 'changed', + 'content_translation_uid', + 'content_translation_created', + 'content_translation_changed', + ] + ); + } + + $base_values = array_diff_key( + $entity->toArray(), + array_combine($keys_to_ignore, $keys_to_ignore) + ); + // Exclude comment fields. + if ($entity instanceof ContentEntityInterface) { + $base_values = array_filter($base_values, function ($property_name) use ($entity) { + return strpos($property_name, 'comment_') !== 0 || !($entity->get($property_name) instanceof CommentFieldItemList); + }, ARRAY_FILTER_USE_KEY); + } + + return array_merge( + $base_values, + $this->getParagraphFieldValues($entity, $langcode) + ); + } + + /** + * Returns the actual number of paragraph entities. + */ + protected function getActualParagraphsCount() :int { + $paragraphs_data = \Drupal::database() + ->select('paragraphs_item', 'p') + ->fields('p') + ->orderBy('p.id') + ->orderBy('p.revision_id') + ->execute() + ->fetchAll(\PDO::FETCH_ASSOC); + return count($paragraphs_data); + } + + /** + * Returns the actual number of paragraph revisions. + */ + protected function getActualParagraphRevisionsCount() :int { + $db = \Drupal::database(); + if (!$db->schema()->tableExists('paragraphs_item_revision')) { + return $this->getActualParagraphsCount(); + } + $paragraph_revisions_data = \Drupal::database() + ->select('paragraphs_item_revision', 'pr') + ->fields('pr') + ->orderBy('pr.id') + ->orderBy('pr.revision_id') + ->execute() + ->fetchAll(\PDO::FETCH_ASSOC); + return count($paragraph_revisions_data); + } + + /** + * Returns the actual number of paragraph entities. + */ + protected function getActualParagraphRevisionTranslationsCount() :int { + $db = \Drupal::database(); + if (!$db->schema()->tableExists('paragraphs_item_revision_field_data')) { + return $this->getActualParagraphRevisionsCount(); + } + $paragraphs_revision_translations_data = $db + ->select('paragraphs_item_revision_field_data', 'prt') + ->fields('prt') + ->orderBy('prt.id') + ->orderBy('prt.revision_id') + ->execute() + ->fetchAll(\PDO::FETCH_ASSOC); + return count($paragraphs_revision_translations_data); + } + +}