View source
<?php
namespace Drupal\replication\Normalizer;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\multiversion\Entity\Index\MultiversionIndexFactory;
use Drupal\multiversion\Entity\WorkspaceInterface;
use Drupal\replication\Event\ReplicationContentDataAlterEvent;
use Drupal\replication\Event\ReplicationDataEvents;
use Drupal\replication\UsersMapping;
use Drupal\serialization\Normalizer\FieldableEntityNormalizerTrait;
use Drupal\serialization\Normalizer\NormalizerBase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class ContentEntityNormalizer extends NormalizerBase implements DenormalizerInterface {
use FieldableEntityNormalizerTrait;
protected $supportedInterfaceOrClass = [
'Drupal\\Core\\Entity\\ContentEntityInterface',
];
protected $indexFactory;
protected $languageManager;
protected $selectionManager;
protected $usersMapping;
protected $dispatcher;
private $moduleHandler;
public function __construct(EntityManagerInterface $entity_manager, MultiversionIndexFactory $index_factory, LanguageManagerInterface $language_manager, UsersMapping $users_mapping, ModuleHandlerInterface $module_handler, SelectionPluginManagerInterface $selection_manager = NULL, EventDispatcherInterface $event_dispatcher = NULL) {
$this->entityManager = $entity_manager;
$this->indexFactory = $index_factory;
$this->languageManager = $language_manager;
$this->usersMapping = $users_mapping;
$this->moduleHandler = $module_handler;
$this->selectionManager = $selection_manager;
$this->dispatcher = $event_dispatcher;
}
public function normalize($entity, $format = NULL, array $context = []) {
$workspace = isset($entity->workspace->entity) ? $entity->workspace->entity : null;
$rev_tree_index = $this->indexFactory
->get('multiversion.entity_index.rev.tree', $workspace);
$entity_type_id = $context['entity_type'] = $entity
->getEntityTypeId();
$entity_type = $this->entityManager
->getDefinition($entity_type_id);
$id_key = $entity_type
->getKey('id');
$revision_key = $entity_type
->getKey('revision');
$uuid_key = $entity_type
->getKey('uuid');
$entity_uuid = $entity
->uuid();
$entity_default_language = $entity
->language();
$entity_languages = $entity
->getTranslationLanguages();
$data = [
'@context' => [
'_id' => '@id',
'@language' => $entity_default_language
->getId(),
],
'@type' => $entity_type_id,
'_id' => $entity_uuid,
];
if (!empty($entity->_rev->value)) {
$data['_rev'] = $entity->_rev->value;
}
$field_definitions = $entity
->getFieldDefinitions();
foreach ($entity_languages as $entity_language) {
$translation = $entity
->getTranslation($entity_language
->getId());
$data[$entity_language
->getId()] = [
'@context' => [
'@language' => $entity_language
->getId(),
],
];
foreach ($translation as $name => $field) {
$field_type = $field_definitions[$name]
->getType();
$items = $this->serializer
->normalize($field, $format, $context);
if ($field_type == 'password') {
continue;
}
$data[$entity_language
->getId()][$name] = $items;
}
if (isset($translation->_deleted->value) && $translation->_deleted->value == TRUE) {
$data[$entity_language
->getId()]['_deleted'] = TRUE;
$data['_deleted'] = TRUE;
}
elseif (isset($data[$entity_language
->getId()]['_deleted'])) {
unset($data[$entity_language
->getId()]['_deleted']);
}
}
if (!empty($entity->_rev->revisions)) {
$data['_revisions']['ids'] = $entity->_rev->revisions;
$data['_revisions']['start'] = count($data['_revisions']['ids']);
}
if (!empty($context['query']['conflicts'])) {
$conflicts = $rev_tree_index
->getConflicts($entity_uuid);
foreach ($conflicts as $rev => $status) {
$data['_conflicts'][] = $rev;
}
}
unset($data['workspace'], $data[$id_key], $data[$revision_key], $data[$uuid_key]);
foreach ($entity_languages as $entity_language) {
$langcode = $entity_language
->getId();
unset($data[$langcode]['workspace'], $data[$langcode][$id_key], $data[$langcode][$revision_key], $data[$langcode][$uuid_key]);
}
$event = new ReplicationContentDataAlterEvent($entity, $data, $format, $context);
$this->dispatcher
->dispatch(ReplicationDataEvents::ALTER_CONTENT_DATA, $event);
return $event
->getData();
}
public function denormalize($data, $class, $format = NULL, array $context = []) {
$entity_type_id = NULL;
$entity_uuid = NULL;
$entity_id = NULL;
$default_langcode = $data['@context']['@language'];
$site_languages = $this->languageManager
->getLanguages();
if (empty($entity_uuid) && !empty($data['_id'])) {
$entity_uuid = $data['_id'];
}
else {
throw new UnexpectedValueException('The uuid value is missing.');
}
if (isset($data['@type'])) {
$entity_type_id = $data['@type'];
}
elseif (!empty($context['entity_type'])) {
$entity_type_id = $context['entity_type'];
}
if (!empty($entity_uuid)) {
$uuid_index = isset($context['workspace']) && $context['workspace'] instanceof WorkspaceInterface ? $this->indexFactory
->get('multiversion.entity_index.uuid', $context['workspace']) : $this->indexFactory
->get('multiversion.entity_index.uuid');
if ($record = $uuid_index
->get($entity_uuid)) {
$entity_id = $record['entity_id'];
if (empty($entity_type_id)) {
$entity_type_id = $record['entity_type_id'];
}
elseif ($entity_type_id != $record['entity_type_id']) {
throw new UnexpectedValueException('The entity_type value does not match the existing UUID record.');
}
}
}
if (empty($entity_type_id)) {
throw new UnexpectedValueException('The entity_type value is missing.');
}
$rev = null;
if (isset($data['_rev'])) {
$rev = $data['_rev'];
}
$revisions = [];
if (isset($data['_revisions']['start']) && isset($data['_revisions']['ids'])) {
$revisions = $data['_revisions'];
}
$entity_type = $this->entityManager
->getDefinition($entity_type_id);
$id_key = $entity_type
->getKey('id');
$revision_key = $entity_type
->getKey('revision');
$bundle_key = $entity_type
->getKey('bundle');
$translations = [];
foreach ($data as $key => $translation) {
if (in_array($key[0], [
'_',
'@',
])) {
continue;
}
elseif (isset($site_languages[$key]) || $key === LanguageInterface::LANGCODE_NOT_SPECIFIED || $key === LanguageInterface::LANGCODE_NOT_APPLICABLE) {
$translations[$key] = $this
->denormalizeTranslation($translation, $entity_id, $entity_uuid, $entity_type_id, $bundle_key, $entity_type, $id_key, $context, $rev, $revisions);
}
elseif (is_array($translation) && $this->moduleHandler
->moduleExists('language')) {
$language = ConfigurableLanguage::createFromLangcode($key);
$language
->save();
$translations[$key] = $this
->denormalizeTranslation($translation, $entity_id, $entity_uuid, $entity_type_id, $bundle_key, $entity_type, $id_key, $context, $rev, $revisions);
}
}
$storage = $this->entityManager
->getStorage($entity_type_id);
if ($entity_id) {
if ($entity = $storage
->load($entity_id) ?: $storage
->loadDeleted($entity_id)) {
if (!empty($translations[$entity
->language()
->getId()])) {
$translation = $translations[$entity
->language()
->getId()];
unset($translation['default_langcode']);
if ($entity_type
->hasKey('bundle')) {
$this
->extractBundleData($translation, $entity_type);
}
$this
->denormalizeFieldData($translation, $entity, $format, $context);
}
}
elseif (isset($translations[$default_langcode][$id_key])) {
unset($translations[$default_langcode][$id_key], $translations[$default_langcode][$revision_key]);
$entity_id = NULL;
$entity = $this
->createEntityInstance($translations[$default_langcode], $entity_type, $format, $context);
}
}
else {
$entity = NULL;
if (!empty($bundle_key) && !empty($translations[$default_langcode][$bundle_key])) {
unset($translations[$default_langcode][$id_key], $translations[$default_langcode][$revision_key]);
$entity = $this
->createEntityInstance($translations[$default_langcode], $entity_type, $format, $context);
}
elseif ($entity_type_id === 'file' && !empty($translations[$default_langcode])) {
if (isset($translations[$default_langcode][$id_key])) {
unset($translations[$default_langcode][$id_key]);
}
if (isset($translations[$default_langcode][$revision_key])) {
unset($translations[$default_langcode][$revision_key]);
}
$translations[$default_langcode]['status'][0]['value'] = FILE_STATUS_PERMANENT;
$translations[$default_langcode]['uid'][0]['target_id'] = $this->usersMapping
->getUidFromConfig();
$entity = $this
->createEntityInstance($translations[$default_langcode], $entity_type, $format, $context);
}
}
foreach ($site_languages as $site_language) {
$langcode = $site_language
->getId();
if ($entity
->language()
->getId() != $langcode && isset($translations[$langcode])) {
$entity
->addTranslation($langcode, $translations[$langcode]);
}
}
if ($entity_id) {
$entity
->enforceIsNew(FALSE);
$entity
->setNewRevision(FALSE);
$entity->_rev->is_stub = FALSE;
}
Cache::invalidateTags([
$entity_type_id . '_list',
]);
return $entity;
}
private function denormalizeTranslation($translation, $entity_id, $entity_uuid, $entity_type_id, $bundle_key, $entity_type, $id_key, $context, $rev = null, array $revisions = []) {
if (isset($rev)) {
$translation['_rev'] = [
[
'value' => $rev,
],
];
}
if (isset($revisions['start']) && isset($revisions['ids'])) {
$translation['_rev'][0]['revisions'] = $revisions['ids'];
}
if (isset($entity_uuid)) {
$translation['uuid'][0]['value'] = $entity_uuid;
}
$deleted = isset($translation['_deleted']) ? $translation['_deleted'] : FALSE;
$translation['_deleted'] = [
[
'value' => $deleted,
],
];
if ($entity_id) {
$translation[$id_key] = [
'value' => $entity_id,
];
}
$bundle_id = $entity_type_id;
if ($entity_type
->hasKey('bundle')) {
if (!empty($translation[$bundle_key][0]['value'])) {
$bundle_id = $translation[$bundle_key][0]['value'];
$translation[$bundle_key] = $bundle_id;
}
elseif (!empty($translation[$bundle_key][0]['target_id'])) {
$bundle_id = $translation[$bundle_key][0]['target_id'];
$translation[$bundle_key] = $bundle_id;
}
}
foreach ($translation as $field_name => $field_info) {
if (!is_array($field_info)) {
continue;
}
foreach ($field_info as $delta => $item) {
if (isset($item['target_uuid'])) {
$translation[$field_name][$delta] = $item;
$fields = $this->entityManager
->getFieldDefinitions($entity_type_id, $bundle_id);
$settings = $fields[$field_name]
->getSettings();
$target_entity_uuid = $item['target_uuid'];
$type = $fields[$field_name]
->getType();
if ($type == 'link' && isset($item['entity_type_id'])) {
$target_entity_type_id = $item['entity_type_id'];
}
else {
$target_entity_type_id = $settings['target_type'];
}
if ($target_entity_type_id === 'user') {
$translation[$field_name] = $this->usersMapping
->mapReferenceField($translation, $field_name);
continue;
}
if (isset($settings['handler_settings']['target_bundles'])) {
$target_bundle_id = reset($settings['handler_settings']['target_bundles']);
}
else {
$bundles = $this->entityManager
->getBundleInfo($target_entity_type_id);
$target_bundle_id = key($bundles);
}
$target_entity = null;
$uuid_index = isset($context['workspace']) && $context['workspace'] instanceof WorkspaceInterface ? $this->indexFactory
->get('multiversion.entity_index.uuid', $context['workspace']) : $this->indexFactory
->get('multiversion.entity_index.uuid');
if ($target_entity_info = $uuid_index
->get($target_entity_uuid)) {
$target_entity = $this->entityManager
->getStorage($target_entity_info['entity_type_id'])
->load($target_entity_info['entity_id']);
}
if (!empty($item['entity_type_id'])) {
$bundle_key = $this->entityManager
->getStorage($item['entity_type_id'])
->getEntityType()
->getKey('bundle');
if (!empty($item[$bundle_key])) {
$target_bundle_id = $item[$bundle_key];
}
}
if ($type == 'link' && $target_entity) {
$id = $target_entity
->id();
$translation[$field_name][$delta]['uri'] = "entity:{$target_entity_type_id}/{$id}";
}
elseif ($target_entity) {
$translation[$field_name][$delta]['target_id'] = $target_entity
->id();
if ($type === 'entity_reference_revisions') {
$revision_key = $target_entity
->getEntityType()
->getKey('revision');
$translation[$field_name][$delta]['target_revision_id'] = $target_entity->{$revision_key}->value;
}
}
else {
$options['target_type'] = $target_entity_type_id;
if (isset($settings['handler_settings'])) {
$options['handler_settings'] = $settings['handler_settings'];
}
$selection_instance = $this->selectionManager
->getInstance($options);
$target_entity = $selection_instance
->createNewEntity($target_entity_type_id, $target_bundle_id, rand(), 1);
if (isset($context['workspace']) && $context['workspace'] instanceof WorkspaceInterface && $target_entity
->getEntityType()
->get('workspace') !== FALSE) {
$target_entity->workspace->target_id = $context['workspace']
->id();
}
$target_entity->uuid->value = $target_entity_uuid;
$target_entity->_rev->is_stub = TRUE;
$translation[$field_name][$delta]['target_id'] = NULL;
$translation[$field_name][$delta]['entity'] = $target_entity;
}
if (isset($translation[$field_name][$delta]['entity_type_id'])) {
unset($translation[$field_name][$delta]['entity_type_id']);
}
if (isset($translation[$field_name][$delta]['target_uuid'])) {
unset($translation[$field_name][$delta]['target_uuid']);
}
}
}
}
if ($entity_type_id == 'menu_link_content' && !empty($translation['parent'][0]['value'])) {
$translation['parent'][0]['value'] = $this
->denormalizeMenuLinkParent($translation['parent'][0]['value'], $context);
}
if (!empty($translation['comment']) && is_array($translation['comment'])) {
foreach ($translation['comment'] as $delta => $item) {
if (empty($item['cid'])) {
unset($translation['comment'][$delta]);
}
}
}
if ($entity_type_id == 'comment' && isset($translation['name'])) {
unset($translation['name']);
}
foreach ([
'@context',
'@type',
'_id',
'_revisions',
'changed',
] as $key) {
if (isset($translation[$key])) {
unset($translation[$key]);
}
}
return $translation;
}
private function createEntityInstance(array $data, EntityTypeInterface $entity_type, $format, array $context = []) {
if ($entity_type
->entityClassImplements(FieldableEntityInterface::class)) {
if ($entity_type
->hasKey('bundle')) {
$create_params = $this
->extractBundleData($data, $entity_type);
}
else {
$create_params = [];
}
$entity = $this->entityManager
->getStorage($entity_type
->id())
->create($create_params);
$this
->denormalizeFieldData($data, $entity, $format, $context);
}
else {
$entity = $this->entityManager
->getStorage($entity_type
->id())
->create($data);
}
return $entity;
}
protected function denormalizeMenuLinkParent($data, $context) {
if (strpos($data, 'menu_link_content') === 0) {
list($type, $uuid, $id) = explode(':', $data);
if ($type === 'menu_link_content' && $uuid && is_numeric($id)) {
$storage = $this->entityManager
->getStorage('menu_link_content');
$parent = $storage
->loadByProperties([
'uuid' => $uuid,
]);
$parent = reset($parent);
if ($parent instanceof MenuLinkContentInterface && $parent
->id() && $parent
->id() != $id) {
return $type . ':' . $uuid . ':' . $parent
->id();
}
elseif (!$parent) {
$parent = $storage
->create([
'uuid' => $uuid,
'link' => 'internal:/',
]);
if (isset($context['workspace']) && $context['workspace'] instanceof WorkspaceInterface) {
$parent->workspace->target_id = $context['workspace']
->id();
}
$parent->_rev->is_stub = TRUE;
$parent
->save();
return $type . ':' . $uuid . ':' . $parent
->id();
}
}
}
return $data;
}
}