content_deploy-1.0.1/src/Importer/ContentImporter.php
src/Importer/ContentImporter.php
<?php
namespace Drupal\content_deploy\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_deploy']['entity_type'])) {
$entity_type_id = $decoded_entity['_content_deploy']['entity_type'];
}
else {
return NULL;
}
// Replace a menu link to a node with an actual one.
if ($entity_type_id == 'menu_link_content' && !empty($decoded_entity["_content_deploy"]["menu_entity_link"])) {
$decoded_entity = $this->alterMenuLink($decoded_entity);
}
$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'];
}
// Replace a Path Alias to a node with an actual one.
if ($entity_type_id == 'path_alias' && !empty($decoded_entity["_content_deploy"]["path_alias"])) {
$decoded_entity = $this->alterPathAlias($decoded_entity);
}
$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() === 1 || (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();
}
}
/**
* Replaces a link to a node with an actual one.
*
* @param array $decoded_entity
* Array of entity values.
*
* @return array
* Array of entity values with the link values changed.
*/
protected function alterMenuLink(array $decoded_entity) {
$referenced_entity_uuid = reset($decoded_entity["_content_deploy"]["menu_entity_link"]);
$referenced_entity_type = key($decoded_entity["_content_deploy"]["menu_entity_link"]);
if ($referenced_entity = \Drupal::service('entity.repository')->loadEntityByUuid($referenced_entity_type, $referenced_entity_uuid)) {
$url = $referenced_entity->toUrl();
$decoded_entity["link"][0]["uri"] = $url->toUriString();
if ($url->isRouted()) {
$route_name = $url->getRouteName();
foreach (array_keys($this->entityTypeManager->getDefinitions()) as $entity_type_id) {
if ($route_name == "entity.{$entity_type_id}.canonical" && isset($url->getRouteParameters()[$entity_type_id])) {
$uri = "entity:{$entity_type_id}/" . $url->getRouteParameters()[$entity_type_id];
}
}
}else{
//$uri = $url->toUriString();
$uri = $url->getUri();
}
$decoded_entity["link"][0]["uri"] = $uri;
}
return $decoded_entity;
}
/**
* Replaces a path to a node with an actual one.
*
* @param array $decoded_entity
* Array of entity values.
*
* @return array
* Array of entity values with the path values changed.
*/
protected function alterPathAlias(array $decoded_entity) {
$referenced_entity_uuid = reset($decoded_entity["_content_deploy"]["path_alias"]);
$referenced_entity_type = key($decoded_entity["_content_deploy"]["path_alias"]);
if ($referenced_entity = \Drupal::service('entity.repository')->loadEntityByUuid($referenced_entity_type, $referenced_entity_uuid)) {
$decoded_entity["path"][0]["value"] = '/' . $referenced_entity_type . '/' . $referenced_entity->id();
}
return $decoded_entity;
}
/**
* @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) {
// TODO: Do we need to test isset($field_values[$property_name])?
// Presumably if $property_name is coming from getCustomSerializedPropertyNames()
// there ought to be a corresponding $field_values by that 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_deploy')
->error($validation->getMessage());
}
}
}
return $valid;
}
/**
* @return array
*/
public function getContext() {
return $this->context;
}
/**
* @param array $context
*/
public function setContext($context) {
$this->context = $context;
}
}