View source
<?php
namespace Drupal\entity_share_client\Service;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\entity_share_client\Entity\RemoteInterface;
use Drupal\entity_share_client\Event\RelationshipFieldValueEvent;
use Drupal\file\FileInterface;
use Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
use GuzzleHttp\Exception\ClientException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Serializer\SerializerInterface;
class JsonapiHelper implements JsonapiHelperInterface {
use StringTranslationTrait;
protected $jsonapiDocumentTopLevelNormalizer;
protected $resourceTypeRepository;
protected $bundleInfos;
protected $entityDefinitions;
protected $entityDefinitionUpdateManager;
protected $entityTypeManager;
protected $streamWrapperManager;
protected $languageManager;
protected $remoteManager;
protected $eventDispatcher;
protected $fileHttpClient;
protected $httpClient;
protected $remote;
protected $importedEntities;
public function __construct(SerializerInterface $serializer, JsonApiDocumentTopLevelNormalizer $jsonapi_document_top_level_normalizer, ResourceTypeRepository $resource_type_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, StreamWrapperManagerInterface $stream_wrapper_manager, LanguageManagerInterface $language_manager, RemoteManagerInterface $remote_manager, EventDispatcherInterface $event_dispatcher) {
$this->jsonapiDocumentTopLevelNormalizer = $jsonapi_document_top_level_normalizer;
$this->jsonapiDocumentTopLevelNormalizer
->setSerializer($serializer);
$this->resourceTypeRepository = $resource_type_repository;
$this->bundleInfos = $entity_type_bundle_info
->getAllBundleInfo();
$this->entityDefinitions = $entity_type_manager
->getDefinitions();
$this->entityDefinitionUpdateManager = $entity_definition_update_manager;
$this->entityTypeManager = $entity_type_manager;
$this->streamWrapperManager = $stream_wrapper_manager;
$this->languageManager = $language_manager;
$this->remoteManager = $remote_manager;
$this->eventDispatcher = $event_dispatcher;
$this->importedEntities = [];
}
public function buildEntitiesOptions(array $json_data) {
$options = [];
foreach ($this
->prepareData($json_data) as $data) {
$this
->addOptionFromJson($options, $data);
}
return $options;
}
public function extractEntity(array $data) {
$prepared_json = [
'data' => [
'type' => $data['type'],
'attributes' => $data['attributes'],
],
];
$parsed_type = explode('--', $data['type']);
return $this->jsonapiDocumentTopLevelNormalizer
->denormalize($prepared_json, NULL, 'api_json', [
'resource_type' => $this->resourceTypeRepository
->get($parsed_type[0], $parsed_type[1]),
]);
}
public function updateRelationships(ContentEntityInterface $entity, array $data) {
if (isset($data['relationships'])) {
$resource_type = $this->resourceTypeRepository
->get($entity
->getEntityTypeId(), $entity
->bundle());
foreach ($data['relationships'] as $field_name => $field_data) {
$field_name = $resource_type
->getInternalName($field_name);
$field = $entity
->get($field_name);
if ($this
->relationshipHandleable($field)) {
$field_values = [];
if ($field_data['data'] != NULL && isset($field_data['links']) && isset($field_data['links']['related'])) {
$referenced_entities_response = $this
->getHttpClient()
->get($field_data['links']['related'])
->getBody()
->getContents();
$referenced_entities_json = Json::decode($referenced_entities_response);
if (!isset($referenced_entities_json['errors'])) {
$referenced_entities_ids = $this
->importEntityListData($referenced_entities_json['data']);
$main_property = $field
->getItemDefinition()
->getMainPropertyName();
foreach ($this
->prepareData($field_data['data']) as $key => $field_value_data) {
if (isset($referenced_entities_ids[$key])) {
$field_value = [
$main_property => $referenced_entities_ids[$key],
];
if (isset($field_value_data['meta'])) {
$field_value += $field_value_data['meta'];
}
$event = new RelationshipFieldValueEvent($field, $field_value);
$this->eventDispatcher
->dispatch(RelationshipFieldValueEvent::EVENT_NAME, $event);
$field_values[] = $event
->getFieldValue();
}
}
}
}
$entity
->set($field_name, $field_values);
}
}
$entity
->save();
}
}
public function handlePhysicalFiles(ContentEntityInterface $entity, array &$data) {
if ($entity instanceof FileInterface) {
$remote_uri = $data['attributes']['uri']['value'];
$remote_url = $data['attributes']['uri']['url'];
$stream_wrapper = $this->streamWrapperManager
->getViaUri($remote_uri);
$directory_uri = $stream_wrapper
->dirname($remote_uri);
if (file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY)) {
try {
$file_content = $this
->getFileHttpClient()
->get($remote_url)
->getBody()
->getContents();
file_put_contents($remote_uri, $file_content);
} catch (ClientException $e) {
drupal_set_message($this
->t('Missing file: %url', [
'%url' => $remote_url,
]), 'warning');
}
}
else {
drupal_set_message($this
->t('Impossible to write in the directory %directory', [
'%directory' => $directory_uri,
]), 'error');
}
}
}
public function setRemote(RemoteInterface $remote) {
$this->remote = $remote;
}
public function importEntityListData(array $entity_list_data) {
$imported_entity_ids = [];
foreach ($this
->prepareData($entity_list_data) as $entity_data) {
$parsed_type = explode('--', $entity_data['type']);
$entity_type = $this->entityDefinitionUpdateManager
->getEntityType($parsed_type[0]);
$entity_keys = $entity_type
->getKeys();
$this
->prepareEntityData($entity_data, $entity_keys);
$data_langcode = $entity_data['attributes'][$entity_keys['langcode']];
if (isset($entity_keys['label'])) {
$entity_label = $entity_data['attributes'][$entity_keys['label']];
}
else {
$entity_label = $parsed_type[0];
}
if (!$this
->dataLanguageExists($data_langcode, $entity_label)) {
continue;
}
$existing_entities = $this->entityTypeManager
->getStorage($parsed_type[0])
->loadByProperties([
'uuid' => $entity_data['attributes'][$entity_keys['uuid']],
]);
$entity = $this
->extractEntity($entity_data);
if (empty($existing_entities)) {
$entity
->save();
$imported_entity_ids[] = $entity
->id();
$this->importedEntities[] = $entity
->uuid();
$this
->updateRelationships($entity, $entity_data);
$this
->handlePhysicalFiles($entity, $entity_data);
if (method_exists($entity, 'setChangedTime')) {
$entity
->setChangedTime($entity_data['attributes']['changed']);
}
$entity
->save();
}
else {
$existing_entity = array_shift($existing_entities);
$imported_entity_ids[] = $existing_entity
->id();
if (!in_array($existing_entity
->uuid(), $this->importedEntities)) {
$this->importedEntities[] = $existing_entity
->uuid();
$has_translation = $existing_entity
->hasTranslation($data_langcode);
if ($has_translation) {
$resource_type = $this->resourceTypeRepository
->get($entity
->getEntityTypeId(), $entity
->bundle());
$existing_translation = $existing_entity
->getTranslation($data_langcode);
foreach ($entity_data['attributes'] as $field_name => $value) {
$field_name = $resource_type
->getInternalName($field_name);
$existing_translation
->set($field_name, $entity
->get($field_name)
->getValue());
}
$existing_translation
->save();
}
else {
$translation = $entity
->toArray();
$existing_entity
->addTranslation($data_langcode, $translation);
$existing_entity
->save();
$existing_translation = $existing_entity
->getTranslation($data_langcode);
}
$this
->updateRelationships($existing_translation, $entity_data);
$this
->handlePhysicalFiles($existing_translation, $entity_data);
if (method_exists($existing_translation, 'setChangedTime')) {
$existing_translation
->setChangedTime($entity_data['attributes']['changed']);
}
$existing_translation
->save();
}
}
}
return $imported_entity_ids;
}
protected function addOptionFromJson(array &$options, array $data, $level = 0) {
$prepared_json = [
'data' => [
'type' => $data['type'],
'attributes' => $data['attributes'],
],
];
$parsed_type = explode('--', $data['type']);
$entity = $this->jsonapiDocumentTopLevelNormalizer
->denormalize($prepared_json, NULL, 'api_json', [
'resource_type' => $this->resourceTypeRepository
->get($parsed_type[0], $parsed_type[1]),
]);
$this
->addOption($options, $entity, $parsed_type[0], $parsed_type[1], $level);
}
protected function addOption(array &$options, ContentEntityInterface $entity, $entity_type_id, $bundle_id, $level = 0) {
$indentation = '';
for ($i = 1; $i <= $level; $i++) {
$indentation .= '<div class="indentation"> </div>';
}
$label = new FormattableMarkup($indentation . '@label', [
'@label' => $entity
->label(),
]);
$status_info = $this
->getStatusInfo($entity, $entity_type_id);
$options[$entity
->uuid()] = [
'label' => $label,
'type' => $entity
->getEntityType()
->getLabel(),
'bundle' => $this->bundleInfos[$entity_type_id][$bundle_id]['label'],
'language' => $this
->getEntityLanguageLabel($entity),
'status' => $status_info['label'],
'#attributes' => [
'class' => [
$status_info['class'],
],
],
];
}
public function prepareData(array $data) {
if ($this
->isNumericArray($data)) {
return $data;
}
else {
return [
$data,
];
}
}
protected function isNumericArray(array $array) {
foreach ($array as $a => $b) {
if (!is_int($a)) {
return FALSE;
}
}
return TRUE;
}
protected function relationshipHandleable(FieldItemListInterface $field) {
$relationship_handleable = FALSE;
if ($field instanceof EntityReferenceFieldItemListInterface) {
$settings = $field
->getItemDefinition()
->getSettings();
if (isset($settings['target_type'])) {
$relationship_handleable = !$this
->isUserOrConfigEntity($settings['target_type']);
}
elseif (isset($settings['entity_type_ids'])) {
foreach ($settings['entity_type_ids'] as $entity_type_id) {
$relationship_handleable = !$this
->isUserOrConfigEntity($entity_type_id);
if (!$relationship_handleable) {
break;
}
}
}
}
return $relationship_handleable;
}
protected function getEntityLanguageLabel(ContentEntityInterface $entity) {
$langcode = $entity
->get('langcode')->value;
$language = $this->languageManager
->getLanguage($langcode);
if (is_null($language)) {
$language_list = LanguageManager::getStandardLanguageList();
if (isset($language_list[$langcode])) {
$entity_language = $language_list[$langcode][0] . ' ' . $this
->t('(not enabled)', [], [
'context' => 'language',
]);
}
else {
$entity_language = $this
->t('Entity in an unsupported language.');
}
}
else {
$entity_language = $language
->getName();
}
return $entity_language;
}
protected function getFileHttpClient() {
if (!$this->fileHttpClient) {
$this->fileHttpClient = $this->remoteManager
->prepareClient($this->remote);
}
return $this->fileHttpClient;
}
protected function getHttpClient() {
if (!$this->httpClient) {
$this->httpClient = $this->remoteManager
->prepareJsonApiClient($this->remote);
}
return $this->httpClient;
}
protected function prepareEntityData(array &$data, array $entity_keys) {
unset($data['attributes'][$entity_keys['id']]);
if (isset($entity_keys['revision']) && !empty($entity_keys['revision'])) {
unset($data['attributes'][$entity_keys['revision']]);
}
unset($data['attributes'][$entity_keys['default_langcode']]);
if (isset($data['attributes']['path'])) {
unset($data['attributes']['path']);
}
}
protected function dataLanguageExists($langcode, $entity_label) {
if (is_null($this->languageManager
->getLanguage($langcode))) {
drupal_set_message($this
->t('Trying to import an entity (%entity_label) in a disabled language.', [
'%entity_label' => $entity_label,
]), 'error');
return FALSE;
}
return TRUE;
}
protected function getStatusInfo(ContentEntityInterface $entity, $entity_type_id) {
$status_info = [
'label' => $this
->t('Undefined'),
'class' => 'entity-share-undefined',
];
$existing_entities = $this->entityTypeManager
->getStorage($entity_type_id)
->loadByProperties([
'uuid' => $entity
->uuid(),
]);
if (empty($existing_entities)) {
$status_info = [
'label' => $this
->t('New entity'),
'class' => 'entity-share-new',
];
}
elseif (method_exists($entity, 'getChangedTime')) {
$existing_entity = array_shift($existing_entities);
$entity_language_id = $entity
->language()
->getId();
if ($existing_entity
->hasTranslation($entity_language_id)) {
$existing_translation = $existing_entity
->getTranslation($entity_language_id);
$entity_changed_time = $entity
->getChangedTime();
$existing_entity_changed_time = $existing_translation
->getChangedTime();
if ($entity_changed_time != $existing_entity_changed_time) {
$status_info = [
'label' => $this
->t('Entities not synchronized'),
'class' => 'entity-share-changed',
];
}
else {
$status_info = [
'label' => $this
->t('Entities synchronized'),
'class' => 'entity-share-up-to-date',
];
}
}
else {
$status_info = [
'label' => $this
->t('New translation'),
'class' => 'entity-share-new',
];
}
}
return $status_info;
}
protected function isUserOrConfigEntity($entity_type_id) {
if ($entity_type_id == 'user') {
return TRUE;
}
elseif ($this->entityDefinitions[$entity_type_id]
->getGroup() == 'configuration') {
return TRUE;
}
return FALSE;
}
}