Skip to content
Snippets Groups Projects
Commit 855234f6 authored by Vadym Abramchuk's avatar Vadym Abramchuk
Browse files

Issue #3336402: Remove field import/export switch/case operators, call field...

Issue #3336402: Remove field import/export switch/case operators, call field processor plugins instead
parent e235997a
No related branches found
No related tags found
1 merge request!37Issue #3336402: Move to plugins
......@@ -21,7 +21,6 @@ services:
- '@single_content_sync.helper'
- '@datetime.time'
- '@plugin.manager.single_content_sync_field_processor'
- '@?inline_block.usage'
single_content_sync.file_generator:
class: Drupal\single_content_sync\ContentFileGenerator
......
......@@ -3,9 +3,6 @@
namespace Drupal\single_content_sync;
use Drupal\block_content\BlockContentInterface;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Component\Utility\Html;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
......@@ -17,7 +14,6 @@ use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\field\FieldConfigInterface;
use Drupal\layout_builder\Plugin\Block\InlineBlock;
use Drupal\media\MediaInterface;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
......@@ -406,222 +402,32 @@ class ContentExporter implements ContentExporterInterface {
* {@inheritdoc}
*/
public function getFieldValue(FieldItemListInterface $field) {
$value = NULL;
$field_definition = $field->getFieldDefinition();
$field_type = $field_definition->getType();
$field_type_not_supported = FALSE;
switch ($field_type) {
case 'boolean':
case 'address':
case 'daterange':
case 'datetime':
case 'email':
case 'geolocation':
case 'link':
case 'telephone':
case 'timestamp':
case 'decimal':
case 'float':
case 'integer':
case 'list_float':
case 'list_integer':
case 'list_string':
case 'text':
case 'string':
case 'string_long':
case 'yearonly':
$value = $field->getValue();
break;
case 'text_long':
case 'text_with_summary':
$value = $field->getValue();
foreach ($value as &$item) {
$text = $item['value'];
if (stristr($text, '<drupal-media') === FALSE) {
continue;
}
$dom = Html::load($text);
$xpath = new \DOMXPath($dom);
$embed_entities = [];
foreach ($xpath->query('//drupal-media[@data-entity-type="media" and normalize-space(@data-entity-uuid)!=""]') as $node) {
/** @var \DOMElement $node */
$uuid = $node->getAttribute('data-entity-uuid');
$media = $this->entityRepository->loadEntityByUuid('media', $uuid);
assert($media === NULL || $media instanceof MediaInterface);
if ($media) {
$embed_entities[] = $this->doExportToArray($media);
}
}
$item['embed_entities'] = $embed_entities;
}
break;
case 'entity_reference':
case 'dynamic_entity_reference':
$value = [];
$ids_by_entity_type = [];
if ($field_type === 'entity_reference') {
$ids_by_entity_type[$field_definition->getSetting('target_type')] = array_column($field->getValue(), 'target_id');
}
else {
foreach ($field->getValue() as $item) {
$ids_by_entity_type[$item['target_type']][] = $item['target_id'];
}
}
foreach ($ids_by_entity_type as $entity_type => $ids) {
$storage = $this->entityTypeManager->getStorage($entity_type);
$entities = $storage->loadMultiple($ids);
foreach ($entities as $child_entity) {
if ($child_entity instanceof FieldableEntityInterface) {
if (!$this->isReferenceCached($child_entity)) {
// Export content entity relation.
$value[] = $this->doExportToArray($child_entity);
}
else {
$value[] = [
'uuid' => $child_entity->uuid(),
'entity_type' => $child_entity->getEntityTypeId(),
'base_fields' => $this->exportBaseValues($child_entity),
'bundle' => $child_entity->bundle(),
];
}
}
// Support basic export of config entity relation.
elseif ($child_entity instanceof ConfigEntityInterface) {
$value[] = [
'type' => 'config',
'dependency_name' => $child_entity->getConfigDependencyName(),
'value' => $child_entity->id(),
];
}
}
}
break;
case 'webform':
$value = [
'target_id' => $field->target_id,
];
break;
case 'entity_reference_revisions':
$value = [];
$ids = array_column($field->getValue(), 'target_id');
$paragraph_storage = $this->entityTypeManager->getStorage('paragraph');
$paragraphs = $paragraph_storage->loadMultiple($ids);
foreach ($paragraphs as $paragraph) {
$value[] = $this->doExportToArray($paragraph);
}
break;
case 'svg_image_field':
case 'file':
case 'image':
$assets = $this->privateTempStore->get('export.assets') ?? [];
$value = [];
$file_storage = $this->entityTypeManager->getStorage('file');
foreach ($field->getValue() as $item) {
$file = $file_storage->load($item['target_id']);
$file_item = [
'uri' => $file->getFileUri(),
'url' => $file->createFileUrl(FALSE),
];
$assets[] = $file_item['uri'];
if (isset($item['alt'])) {
$file_item['alt'] = $item['alt'];
}
if (isset($item['title'])) {
$file_item['title'] = $item['title'];
}
if (isset($item['description'])) {
$file_item['description'] = $item['description'];
}
$value[] = $file_item;
}
$assets = array_unique($assets);
$assets = array_values($assets);
// Let's store all exported assets in the private storage.
// This will be used during exporting all assets to the zip later on.
$this->privateTempStore->set('export.assets', $assets);
break;
case 'metatag':
$field_value = $field->getValue();
$value = !empty($field_value[0]['value'])
? unserialize($field_value[0]['value'], ['allowed_classes' => FALSE])
: [];
break;
case 'layout_section':
$block_storage = $this->entityTypeManager->getStorage('block_content');
$block_list = [];
$sections = [];
foreach ($field->getValue() as $section_array) {
/** @var \Drupal\layout_builder\Section $section */
$section = $section_array['section'];
$sections[] = serialize($section);
$components = $section->getComponents();
foreach ($components as $component) {
if ($component->getPlugin() instanceof InlineBlock) {
$configuration = $component->toArray()['configuration'];
$block = NULL;
if (isset($configuration['block_serialized'])) {
$block = unserialize($configuration['block_serialized'], [
'allowed_classes' => [BlockContent::class],
]);
}
elseif (isset($configuration['block_revision_id'])) {
$block = $block_storage->loadRevision($configuration['block_revision_id']);
}
if ($block) {
$block_list[] = $this->doExportToArray($block);
}
}
}
}
$value = [
'sections' => base64_encode(implode('|', $sections)),
'blocks' => $block_list,
];
break;
default:
$field_type_not_supported = TRUE;
break;
}
$fieldProcessor = $this
->fieldProcessorPluginManager
->getFieldPluginInstance(
$field->getEntity()->getEntityTypeId(),
$field->getEntity()->bundle(),
$field->getName()
);
// Retrieve the exportable value, if plugin exists.
$value = $fieldProcessor
? $fieldProcessor->exportFieldValue($field)
: NULL;
// Alter value by using hook_content_export_field_value_alter().
$this->moduleHandler->alter('content_export_field_value', $value, $field);
// Note that hook is executed even if plugin does not exist; thus, it is
// possible to provide some value even if the field type is not supported.
$this->moduleHandler->alterDeprecated('Deprecated as of single_content_sync 1.4.0; implement SingleContentSyncFieldProcessor plugin instead to provide support for new field types.', 'content_export_field_value', $value, $field);
// @todo: Dispatch field export event.
// Display a message about non-supported field types.
if ($field_type_not_supported && is_null($value)) {
if (!$fieldProcessor && $value === NULL) {
$field_definition = $field->getFieldDefinition();
$this->messenger->addWarning($this->t('The value of %field_label is empty because field type "@field_type" is not exportable out-of-the-box. Check README for a workaround.', [
'%field_label' => $field_definition->getLabel(),
'@field_type' => $field_type,
'@field_type' => $field_definition->getType(),
]));
}
......
......@@ -13,11 +13,6 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\file\FileInterface;
use Drupal\layout_builder\InlineBlockUsageInterface;
use Drupal\layout_builder\Plugin\Block\InlineBlock;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionComponent;
/**
* Creates a helper service to import content.
......@@ -75,13 +70,6 @@ class ContentImporter implements ContentImporterInterface {
*/
protected SingleContentSyncFieldProcessorPluginManagerInterface $fieldProcessorPluginManager;
/**
* The inline block usage service.
*
* @var \Drupal\layout_builder\InlineBlockUsageInterface|null
*/
protected ?InlineBlockUsageInterface $inlineBlockUsage;
/**
* ContentExporter constructor.
*
......@@ -99,8 +87,6 @@ class ContentImporter implements ContentImporterInterface {
* The time service.
* @param \Drupal\single_content_sync\SingleContentSyncFieldProcessorPluginManagerInterface $field_processor_plugin_manager
* The field processor plugin manager.
* @param \Drupal\layout_builder\InlineBlockUsageInterface|null $inline_block_usage
* The inline block usage service. This is optional dependency.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
......@@ -109,8 +95,7 @@ class ContentImporter implements ContentImporterInterface {
FileSystemInterface $file_system,
ContentSyncHelperInterface $content_sync_helper,
TimeInterface $time,
SingleContentSyncFieldProcessorPluginManagerInterface $field_processor_plugin_manager,
InlineBlockUsageInterface $inline_block_usage = NULL
SingleContentSyncFieldProcessorPluginManagerInterface $field_processor_plugin_manager
) {
$this->entityTypeManager = $entity_type_manager;
$this->entityRepository = $entity_repository;
......@@ -119,7 +104,6 @@ class ContentImporter implements ContentImporterInterface {
$this->contentSyncHelper = $content_sync_helper;
$this->time = $time;
$this->fieldProcessorPluginManager = $field_processor_plugin_manager;
$this->inlineBlockUsage = $inline_block_usage;
}
/**
......@@ -269,222 +253,24 @@ class ContentImporter implements ContentImporterInterface {
return;
}
$field_definition = $entity->getFieldDefinition($field_name);
switch ($field_definition->getType()) {
case 'boolean':
case 'address':
case 'daterange':
case 'datetime':
case 'email':
case 'geolocation':
case 'link':
case 'telephone':
case 'timestamp':
case 'decimal':
case 'float':
case 'integer':
case 'list_float':
case 'list_integer':
case 'list_string':
case 'text':
case 'string':
case 'string_long':
case 'yearonly':
$entity->set($field_name, $field_value);
break;
case 'text_long':
case 'text_with_summary':
if (is_array($field_value)) {
foreach ($field_value as $item) {
$embed_entities = $item['embed_entities'] ?? [];
foreach ($embed_entities as $embed_entity) {
$this->doImport($embed_entity);
}
}
}
$entity->set($field_name, $field_value);
break;
case 'entity_reference':
case 'entity_reference_revisions':
case 'dynamic_entity_reference':
$values = [];
foreach ($field_value as $child_entity) {
// Import config relation just by setting target id.
if (isset($child_entity['type']) && $child_entity['type'] === 'config') {
$values[] = [
'target_id' => $child_entity['value'],
];
continue;
}
// If the entity was fully exported we do the full import.
if ($this->isFullEntity($child_entity)) {
$values[] = $this->doImport($child_entity);
continue;
}
$reference_entity = $this->entityRepository->loadEntityByUuid($child_entity['entity_type'], $child_entity['uuid']);
// Create a stub entity without custom field values.
if (!$reference_entity) {
$reference_entity_values = [
'uuid' => $child_entity['uuid'],
];
$definition = $this->entityTypeManager->getDefinition($child_entity['entity_type']);
if ($bundle_key = $definition->getKey('bundle')) {
$reference_entity_values[$bundle_key] = $child_entity['bundle'];
}
$reference_entity = $this->entityTypeManager->getStorage($child_entity['entity_type'])->create($reference_entity_values);
$this->importBaseValues($reference_entity, $child_entity['base_fields']);
$reference_entity->save();
}
$values[] = $reference_entity;
}
$entity->set($field_name, $values);
break;
case 'webform':
$webform_storage = $this->entityTypeManager->getStorage('webform');
if (isset($field_value['target_id'])) {
if ($webform = $webform_storage->load($field_value['target_id'])) {
$entity->set($field_name, $webform);
}
}
break;
case 'svg_image_field':
case 'file':
case 'image':
$file_storage = $this->entityTypeManager->getStorage('file');
$values = [];
foreach ($field_value as $file_item) {
$files = $file_storage->loadByProperties([
'uri' => $file_item['uri'],
]);
/** @var \Drupal\file\FileInterface $file */
if (count($files)) {
$file = reset($files);
}
else {
$file_path = NULL;
// Check if we have a file on the server. This is a case when you do
// import content with assets from a zip file.
if (file_exists($file_item['uri'])) {
$file_path = $file_item['uri'];
}
elseif (file($file_item['url']) !== FALSE) {
$file_path = $file_item['url'];
}
if (!$file_path) {
continue;
}
// Create a file entity with the given uri as the file was already
// imported in the proper directory. If the file is external then
// we don't need to store file locally.
$file = $file_storage->create([
'uid' => 1,
'status' => FileInterface::STATUS_PERMANENT,
'uri' => $file_item['uri'],
]);
$file->save();
}
$file_value = [
'target_id' => $file->id(),
];
if (isset($file_item['alt'])) {
$file_value['alt'] = $file_item['alt'];
}
if (isset($file_item['title'])) {
$file_value['title'] = $file_item['title'];
}
if (isset($file_item['description'])) {
$file_value['description'] = $file_item['description'];
}
$values[] = $file_value;
}
$entity->set($field_name, $values);
break;
case 'metatag':
$entity->set($field_name, [['value' => serialize($field_value)]]);
break;
case 'layout_section':
if (!$this->moduleHandler->moduleExists('layout_builder')) {
throw new \Exception('The layout could not be imported due to the layout_builder module was disabled.');
}
$imported_blocks = [];
$block_list = $field_value['blocks'] ?? [];
// Prepare entity to have id in the database to be used for inline block
// usages.
if ($block_list) {
$this->createOrUpdate($entity);
}
foreach ($block_list as $block) {
/** @var \Drupal\block_content\BlockContentInterface $new_block */
$new_block = $this->doImport($block);
if (!$this->inlineBlockUsage->getUsage($new_block->id())) {
$this->inlineBlockUsage->addUsage($new_block->id(), $entity);
}
$old_revision_id = $block['base_fields']['block_revision_id'];
$imported_blocks[$old_revision_id] = $new_block->getRevisionId();
}
// Get unserialized version of each section.
$base64_sections = base64_decode($field_value['sections'] ?? $field_value);
/** @var \Drupal\layout_builder\Section[] $sections */
$sections = array_map(function (string $section) {
return unserialize($section, [
'allowed_classes' => [Section::class, SectionComponent::class],
]);
}, explode('|', $base64_sections));
foreach ($sections as $section) {
$section_components = $section->getComponents();
foreach ($section_components as $component) {
if ($component->getPlugin() instanceof InlineBlock) {
$configuration = $component->toArray()['configuration'];
if (isset($configuration['block_revision_id']) && isset($imported_blocks[$configuration['block_revision_id']])) {
// Replace the old revision id with a new revision id.
$configuration['block_revision_id'] = $imported_blocks[$configuration['block_revision_id']];
$component->setConfiguration($configuration);
}
}
}
}
$entity->set($field_name, $sections);
break;
// Get the field processor instance and call it to set the field value.
$fieldProcessor = $this
->fieldProcessorPluginManager
->getFieldPluginInstance(
$entity->getEntityTypeId(),
$entity->bundle(),
$field_name
);
if ($fieldProcessor) {
$fieldProcessor->importFieldValue($entity, $field_name, $field_value);
}
// Alter setting a field value during the import by using
// hook_content_import_field_value(). Support of importing a new field type
// can be provided in the hook.
$this->moduleHandler->alter('content_import_field_value', $entity, $field_name, $field_value);
// hook_content_import_field_value().
// This hook was previously used to support importing new field types.
$this->moduleHandler->alterDeprecated('Deprecated as of single_content_sync 1.4.0; implement SingleContentSyncFieldProcessor plugin instead to provide support for new field types.', 'content_import_field_value', $entity, $field_name, $field_value);
// @todo Dispatch the field value import event.
}
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment