Commit 4194ce68 authored by mikelutz's avatar mikelutz Committed by miro_dietiker

Issue #2911243 by mikelutz, heddn: Create field collections field plugin

parent a275a9e3
......@@ -8,7 +8,12 @@
use Drupal\Core\Url;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\Plugin\migrate\source\d7\Field;
use Drupal\field\Plugin\migrate\source\d7\FieldInstance;
use Drupal\field\Plugin\migrate\source\d7\ViewMode;
use Drupal\migrate_drupal\Plugin\migrate\FieldMigration;
use Drupal\paragraphs\Entity\ParagraphsType;
use Drupal\paragraphs\Plugin\migrate\field\FieldCollection;
use Drupal\Core\Render\Element;
/**
......@@ -440,3 +445,107 @@ function paragraphs_library_info_alter(&$libraries, $extension) {
return $libraries;
}
/**
* Implements hook_migration_plugins_alter().
*
* @todo refactor/rethink this when
* https://www.drupal.org/project/drupal/issues/2904765 is resolved
*/
function paragraphs_migration_plugins_alter(array &$migrations) {
foreach ($migrations as $key => &$migration) {
/** @var \Drupal\migrate\Plugin\MigrationPluginManager $migration_plugin_manager */
$migration_plugin_manager = \Drupal::service('plugin.manager.migration');
$migration_stub = $migration_plugin_manager->createStubMigration($migration);
/** @var \Drupal\migrate\Plugin\MigrateSourcePluginManager $source_plugin_manager */
$source_plugin_manager = \Drupal::service('plugin.manager.migrate.source');
$source = NULL;
$configuration = $migration['source'];
$source = $source_plugin_manager->createInstance($migration['source']['plugin'], $configuration, $migration_stub);
if ($source) {
if (is_a($migration['class'], FieldMigration::class, TRUE)) {
// Field storage.
if (is_a($source, Field::class)) {
_paragraphs_migration_entity_type_adjust($migration);
}
// Field instance.
if (is_a($source, FieldInstance::class)) {
_paragraphs_migration_entity_type_adjust($migration);
_paragraphs_migration_bundle_adjust($migration);
$migration['migration_dependencies']['optional']['d7_field_collection_type'] = 'd7_field_collection_type';
$migration['migration_dependencies']['optional']['d7_paragraphs_type'] = 'd7_paragraphs_type';
}
}
// View Modes.
if (is_a($source, ViewMode::class)) {
_paragraphs_migration_entity_type_adjust($migration, 'targetEntityType');
}
}
}
}
/**
* Remove 'field_' prefix from field collection bundles.
*
* @param array $migration
* The migration configuration to process.
*/
function _paragraphs_migration_bundle_adjust(array &$migration) {
if (!isset($migration['process']['bundle'])) {
$migration['process']['bundle'] = [];
}
$bundle_process = $migration['process']['bundle'];
// Try to play nice with other modules altering this, and don't replace
// it outright unless it's unchanged.
if (array_key_exists('plugin', $bundle_process)) {
$bundle_process = [$bundle_process];
}
$bundle_process['paragraphs'] = [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'entity_type',
'expected_value' => 'field_collection_item',
'process' => [
'plugin' => 'substr',
'start' => FieldCollection::FIELD_COLLECTION_PREFIX_LENGTH,
],
];
$migration['process']['bundle'] = $bundle_process;
}
/**
* Map field_collection_item and 'paragraphs_item' fields to 'paragraph'.
*
* @param array $migration
* Thei migration to process.
* @param string $destination
* The process destination.
*/
function _paragraphs_migration_entity_type_adjust(array &$migration, $destination = 'entity_type') {
$entity_type_process = $migration['process'][$destination];
// Try to play with other modules altering this, and don't replace it
// outright unless it's unchanged.
if (!is_array($entity_type_process)) {
$entity_type_process = [
[
'plugin' => 'get',
'source' => 'entity_type',
],
];
}
$entity_type_process['paragraphs'] = [
'plugin' => 'static_map',
'map' => [
'field_collection_item' => 'paragraph',
'paragraphs_item' => 'paragraph',
],
'bypass' => TRUE,
];
$migration['process'][$destination] = $entity_type_process;
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\field;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
/**
* Field Plugin for field collection migrations.
*
* @todo Implement ::processFieldValues()
* @see https://www.drupal.org/project/paragraphs/issues/2911244
*
* @MigrateField(
* id = "field_collection",
* core = {7},
* type_map = {
* "field_collection" = "entity_reference_revisions",
* },
* source_module = "field_collection",
* destination_module = "paragraphs",
* )
*/
class FieldCollection extends FieldPluginBase {
/**
* Length of the 'field_' prefix that field collection prepends to bundles.
*/
const FIELD_COLLECTION_PREFIX_LENGTH = 6;
/**
* {@inheritdoc}
*/
public function processFieldFormatter(MigrationInterface $migration) {
$options_type[0]['map']['field_collection_view'] = 'entity_reference_revisions_entity_view';
$view_mode = [
'field_collection' => [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'type',
'expected_value' => 'field_collection',
'process' => [
'plugin' => 'get',
'source' => 'formatter/settings/view_mode',
],
],
];
$migration->mergeProcessOfProperty('options/type', $options_type);
$migration->mergeProcessOfProperty('options/settings/view_mode', $view_mode);
}
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return ['field_collection_embed' => 'entity_reference_paragraphs']
+ parent::getFieldWidgetMap();
}
/**
* {@inheritdoc}
*/
public function processField(MigrationInterface $migration) {
$settings = [
'field_collection' => [
'plugin' => 'field_collection_field_settings',
],
];
$migration->mergeProcessOfProperty('settings', $settings);
}
/**
* {@inheritdoc}
*/
public function processFieldInstance(MigrationInterface $migration) {
$settings = [
'field_collection' => [
'plugin' => 'field_collection_field_instance_settings',
],
];
$migration->mergeProcessOfProperty('settings', $settings);
}
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\field;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
/**
* Field Plugin for paragraphs migrations.
*
* @todo Implement ::processFieldValues()
* @see https://www.drupal.org/project/paragraphs/issues/2911244
*
* @MigrateField(
* id = "paragraphs",
* core = {7},
* type_map = {
* "paragraphs" = "entity_reference_revisions",
* },
* source_module = "paragraphs",
* destination_module = "paragraphs",
* )
*/
class Paragraphs extends FieldPluginBase {
/**
* {@inheritdoc}
*/
public function processFieldWidget(MigrationInterface $migration) {
parent::processFieldWidget($migration);
$title = [
'paragraphs' => [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'type',
'expected_value' => 'paragraphs',
'process' => [
'plugin' => 'get',
'source' => 'settings/title',
],
],
];
$title_plural = [
'paragraphs' => [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'type',
'expected_value' => 'paragraphs',
'process' => [
'plugin' => 'get',
'source' => 'settings/title_multiple',
],
],
];
$edit_mode = [
'paragraphs' => [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'type',
'expected_value' => 'paragraphs',
'process' => [
'plugin' => 'get',
'source' => 'settings/default_edit_mode',
],
],
];
$add_mode = [
'paragraphs' => [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'type',
'expected_value' => 'paragraphs',
'process' => [
'plugin' => 'get',
'source' => 'settings/add_mode',
],
],
];
$migration->mergeProcessOfProperty('options/settings/title', $title);
$migration->mergeProcessOfProperty('options/settings/title_plural', $title_plural);
$migration->mergeProcessOfProperty('options/settings/edit_mode', $edit_mode);
$migration->mergeProcessOfProperty('options/settings/add_mode', $add_mode);
}
/**
* {@inheritdoc}
*/
public function processFieldFormatter(MigrationInterface $migration) {
$options_type[0]['map']['paragraphs_view'] = 'entity_reference_revisions_entity_view';
$view_mode = [
'paragraphs' => [
'plugin' => 'paragraphs_process_on_value',
'source_value' => 'type',
'expected_value' => 'paragraphs',
'process' => [
'plugin' => 'get',
'source' => 'formatter/settings/view_mode',
],
],
];
$migration->mergeProcessOfProperty('options/type', $options_type);
$migration->mergeProcessOfProperty('options/settings/view_mode', $view_mode);
}
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return ['paragraphs_embed' => 'entity_reference_paragraphs']
+ parent::getFieldWidgetMap();
}
/**
* {@inheritdoc}
*/
public function processField(MigrationInterface $migration) {
$settings = [
'paragraphs' => [
'plugin' => 'paragraphs_field_settings',
],
];
$migration->mergeProcessOfProperty('settings', $settings);
}
/**
* {@inheritdoc}
*/
public function processFieldInstance(MigrationInterface $migration) {
$settings = [
'paragraphs' => [
'plugin' => 'paragraphs_field_instance_settings',
],
];
$migration->mergeProcessOfProperty('settings', $settings);
}
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Row;
use Drupal\paragraphs\Plugin\migrate\field\FieldCollection;
/**
* Configure field instance settings for field collections.
*
* @MigrateProcessPlugin(
* id = "field_collection_field_instance_settings"
* )
*/
class FieldCollectionFieldInstanceSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$type = $row->getSourceProperty('type');
if ($type == 'field_collection') {
$bundles = $this->entityTypeBundleInfo->getBundleInfo('paragraph');
$target_bundle = $row->getSourceProperty('field_name');
// Remove field_ prefix for new bundle.
$target_bundle = substr($target_bundle, FieldCollection::FIELD_COLLECTION_PREFIX_LENGTH);
if (!isset($bundles[$target_bundle])) {
throw new MigrateSkipRowException('No target paragraph bundle found for field_collection');
}
// Enable only this paragraph type for this field.
$weight = 0;
$value['handler_settings']['negate'] = 0;
$value['handler_settings']['target_bundles'] = [$target_bundle => $target_bundle];
$value['handler_settings']['target_bundles_drag_drop'][$target_bundle] = [
'enabled' => TRUE,
'weight' => ++$weight,
];
unset($bundles[$target_bundle]);
foreach ($bundles as $bundle_name => $bundle) {
$value['handler_settings']['target_bundles_drag_drop'][$bundle_name] = [
'enabled' => FALSE,
'weight' => ++$weight,
];
unset($bundles[$bundle_name]);
}
}
return $value;
}
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Configure field instance settings for paragraphs.
*
* @MigrateProcessPlugin(
* id = "field_collection_field_settings"
* )
*/
class FieldCollectionFieldSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if ($row->getSourceProperty('type') == 'field_collection') {
$value['target_type'] = 'paragraph';
}
return $value;
}
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\process;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* Configure field instance settings for paragraphs.
*
* @MigrateProcessPlugin(
* id = "paragraphs_field_instance_settings"
* )
*/
class ParagraphsFieldInstanceSettings extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$type = $row->getSourceProperty('type');
if ($type == 'paragraphs') {
$bundles = $this->entityTypeBundleInfo->getBundleInfo('paragraph');
$target_bundles = [];
if (!empty($value['allowed_bundles'])) {
$target_bundles = array_filter($value['allowed_bundles'], function ($a) {
return $a != -1;
});
$value['handler_settings']['negate'] = 0;
if (empty($target_bundles)) {
$value['handler_settings']['target_bundles'] = NULL;
}
else {
$value['handler_settings']['target_bundles'] = $target_bundles;
}
unset($value['allowed_bundles']);
}
if (!empty($value['bundle_weights'])) {
// Copy the existing weights, and add any new bundles (either from
// a field collection migration happening now, or pre-existing on the
// site at the bottom.
foreach ($value['bundle_weights'] as $bundle_name => $weight) {
$value['handler_settings']['target_bundles_drag_drop'][$bundle_name] = [
'enabled' => array_key_exists($bundle_name, $target_bundles),
'weight' => $weight,
];
}
$other_bundles = array_keys(array_diff_key($bundles, $value['bundle_weights']));
$weight = max($value['bundle_weights']);
foreach ($other_bundles as $bundle_name) {
$value['handler_settings']['target_bundles_drag_drop'][$bundle_name] = [
'enabled' => array_key_exists($bundle_name, $target_bundles),
'weight' => ++$weight,
];
}
unset($value['bundle_weights']);
}
}
return $value;
}
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Configure field instance settings for paragraphs.
*
* @MigrateProcessPlugin(
* id = "paragraphs_field_settings"
* )
*/
class ParagraphsFieldSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if ($row->getSourceProperty('type') == 'paragraphs') {
$value['target_type'] = 'paragraph';
}
return $value;
}
}
<?php
namespace Drupal\paragraphs\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Row;
/**
* Runs a migration process on a value if a condition is met.
*
* The process will run if a source value is equal to an expected value.
* Otherwise returns the original value unchanged.
*
* Configuration Keys:
*
* source_value: (required) string. The source property to check against.
* expected_value: (required) string. The value to check against. If the
* source property described by source_value matches this value, the process
* will be executed.
* process: (required) array. The process array to execute if the source
* property matches the expected value.
*
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*
* @MigrateProcessPlugin(
* id = "paragraphs_process_on_value"
* )
*/
class ParagraphsProcessOnValue extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (empty($this->configuration['source_value'])) {
throw new \InvalidArgumentException("Required argument 'source_value' not set for paragraphs_process_on_value plugin");
}
if (!isset($this->configuration['expected_value'])) {
throw new \InvalidArgumentException("Required argument 'expected_value' not set for paragraphs_process_on_value plugin");
}
if (empty($this->configuration['process']) || !is_array($this->configuration['process'])) {
throw new \InvalidArgumentException("Required argument 'process' not set or invalid for paragraphs_process_on_value plugin");
}
$source_value = $row->getSourceProperty($this->configuration['source_value']);
if (is_null($source_value)) {
throw new MigrateSkipRowException('Argument source_value is not valid for ProcessOnValue plugin');
}
if ($source_value === $this->configuration['expected_value']) {
$process = $this->configuration['process'];
// Append the current working value to the new source we are creating.
$source = $row->getSource();
$source['paragraphs_process_on_value_source_field'] = $value;
// If there is a single process plugin, add the source field. If there
// is an array of process plugins, add the source field to the first one.
if (array_key_exists('plugin', $process)) {
if (empty($process['source'])) {
$process['source'] = 'paragraphs_process_on_value_source_field';
}
}
else {
if (empty($process[0]['source'])) {
$process[0]['source'] = 'paragraphs_process_on_value_source_field';
}
}
$source = $row->getSource();
$source['paragraphs_process_on_value_source_field'] = $value;
$new_row = new Row($source, []);
$migrate_executable->processRow($new_row, [$destination_property => $process]);
return $new_row->getDestinationProperty($destination_property);
}
else {