content_sync-8.x-2.x-dev/src/Importer/ContentImporter.php
src/Importer/ContentImporter.php
<?php namespace Drupal\content_sync\Importer; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\serialization\Normalizer\SerializedColumnNormalizerTrait; use Symfony\Component\Serializer\Serializer; class ContentImporter implements ContentImporterInterface { use SerializedColumnNormalizerTrait; protected $format = 'yaml'; protected $updateEntities = TRUE; protected $context = []; /** * @var \Symfony\Component\Serializer\Serializer */ protected $serializer; /** * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * ContentImporter constructor. */ public function __construct(Serializer $serializer, EntityTypeManagerInterface $entity_type_manager) { $this->serializer = $serializer; $this->entityTypeManager = $entity_type_manager; } public function importEntity($decoded_entity, $context = []) { $context = $this->context + $context; if (!empty($context['entity_type'])) { $entity_type_id = $context['entity_type']; } elseif (!empty($decoded_entity['_content_sync']['entity_type'])) { $entity_type_id = $decoded_entity['_content_sync']['entity_type']; } else { return NULL; } $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); //Exception for parent null -- allowing the term to be displayed on the taxonomy list. if ($entity_type_id == 'taxonomy_term') { if(empty($decoded_entity['parent'])){ $decoded_entity['parent']['target_id'] = 0; } } //Get Translations before denormalize if(!empty($decoded_entity['_translations'])){ $entity_translations = $decoded_entity['_translations']; } $entity = $this->serializer->denormalize($decoded_entity, $entity_type->getClass(), $this->format, $context); if (!empty($entity)) { // Prevent Anonymous User from being saved. if ($entity_type_id == 'user' && !$entity->isNew() && (int) $entity->id() === 0) { return $entity; } $entity = $this->syncEntity($entity); } // Include Translations if ($entity){ if ( isset($entity_translations) && is_array($entity_translations) ) { $this->updateTranslation($entity, $entity_type, $entity_translations, $context); } } return $entity; } /** * Updates translations. * * @param $entity * An entity object. * @param \Drupal\Core\Entity\ContentEntityType $entity_type * A ContentEntityType object. * @param array $entity_translations * An array of translations. * @param $context * The batch context. */ protected function updateTranslation(&$entity, $entity_type, $entity_translations, $context) { foreach ($entity_translations as $langcode => $translation) { // Denormalize. $translation = $this->serializer->denormalize($translation, $entity_type->getClass(), $this->format, $context); // If an entity has a translation - update one, otherwise - add a new one. $entity_translation = $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode) : $entity->addTranslation($langcode); // Get fields definitions. $fields = $translation->getFieldDefinitions(); foreach ($translation as $itemID => $item) { if ($entity_translation->hasField($itemID)){ if ($fields[$itemID]->isTranslatable() == TRUE){ $entity_translation->$itemID->setValue($item->getValue()); } } } // Avoid issues updating revisions. if ($entity_translation->getEntityType()->hasKey('revision')) { $entity_translation->updateLoadedRevisionId(); $entity_translation->setNewRevision(FALSE); } // Save the entity translation. $entity_translation->save(); } } /** * @return string */ public function getFormat() { return $this->format; } /** * Synchronize a given entity. * * @param ContentEntityInterface $entity * The entity to update. * * @return ContentEntityInterface * The updated entity */ protected function syncEntity(ContentEntityInterface $entity) { $preparedEntity = $this->prepareEntity($entity); if ($this->validateEntity($preparedEntity)) { $preparedEntity->save(); return $preparedEntity; } elseif (!$preparedEntity->isNew()) { return $preparedEntity; } return NULL; } /** * Serializes fields which have to be stored serialized. * * @param $entity * The entity to update. * * @return mixed * The entity with the fields being serialized. */ protected function processSerializedFields($entity) { foreach ($entity->getTypedData() as $name => $field_items) { foreach ($field_items as $field_item) { // Determine whether field has serialized properties, // and if so, sanitize. $serialized_property_names = $this->getCustomSerializedPropertyNames($field_item); if (!empty($serialized_property_names)) { $field_values = $field_item->getValue(); foreach ($serialized_property_names as $property_name) { if(isset($field_values[$property_name])){ $field_values[$property_name] = (is_array($field_values[$property_name])) ? serialize($field_values[$property_name]) : $field_values[$property_name]; } } $entity->set($name, $field_values); } } } return $entity; } /** * {@inheritdoc} */ public function prepareEntity(ContentEntityInterface $entity) { $uuid = $entity->uuid(); $original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId()) ->loadByProperties(['uuid' => $uuid]); if (!empty($original_entity)) { $original_entity = reset($original_entity); if (!$this->updateEntities) { return $original_entity; } // Overwrite the received properties. if (!empty($entity->_restSubmittedFields)) { foreach ($entity->_restSubmittedFields as $field_name) { if ($this->isValidEntityField($original_entity, $entity, $field_name)) { $original_entity->set($field_name, $entity->get($field_name) ->getValue()); } } } return $this->processSerializedFields($original_entity); } $duplicate = $entity->createDuplicate(); $entity_type = $entity->getEntityType(); $duplicate->{$entity_type->getKey('uuid')}->value = $uuid; return $this->processSerializedFields($duplicate); } /** * Checks if the entity field needs to be synchronized. * * @param ContentEntityInterface $original_entity * The original entity. * @param ContentEntityInterface $entity * The entity. * @param string $field_name * The field name. * * @return bool * True if the field needs to be synced. */ protected function isValidEntityField(ContentEntityInterface $original_entity, ContentEntityInterface $entity, $field_name) { $valid = TRUE; $entity_keys = $entity->getEntityType()->getKeys(); // Check if the target entity has the field. if (!$entity->hasField($field_name)) { $valid = FALSE; } // Entity key fields need special treatment: together they uniquely // identify the entity. Therefore it does not make sense to modify any of // them. However, rather than throwing an error, we just ignore them as // long as their specified values match their current values. elseif (in_array($field_name, $entity_keys, TRUE)) { // Unchanged values for entity keys don't need access checking. if ($original_entity->get($field_name) ->getValue() === $entity->get($field_name)->getValue() // It is not possible to set the language to NULL as it is // automatically re-initialized. // As it must not be empty, skip it if it is. || isset($entity_keys['langcode']) && $field_name === $entity_keys['langcode'] && $entity->get($field_name)->isEmpty() || $field_name === $entity->getEntityType()->getKey('id') || $entity->getEntityType()->isRevisionable() && $field_name === $entity->getEntityType()->getKey('revision') ) { $valid = FALSE; } } return $valid; } /** * {@inheritdoc} */ public function validateEntity(ContentEntityInterface $entity) { $reflection = new \ReflectionClass($entity); $valid = TRUE; if ($reflection->implementsInterface('\Drupal\user\UserInterface')) { $validations = $entity->validate(); if (count($validations)) { /** * @var ConstraintViolation $validation */ foreach ($validations as $validation) { if (!empty($this->getContext()['skipped_constraints']) && in_array(get_class($validation->getConstraint()), $this->getContext()['skipped_constraints'])) { continue; } $valid = FALSE; \Drupal::logger('content_sync') ->error($validation->getMessage()); } } } return $valid; } /** * @return array */ public function getContext() { return $this->context; } /** * @param array $context */ public function setContext($context) { $this->context = $context; } }