diff --git a/modules/entity_share_client/entity_share_client.routing.yml b/modules/entity_share_client/entity_share_client.routing.yml
index 78e6db6..35fb6e6 100644
--- a/modules/entity_share_client/entity_share_client.routing.yml
+++ b/modules/entity_share_client/entity_share_client.routing.yml
@@ -19,12 +19,11 @@ entity_share_client.admin_content_pull_form:
_admin_route: TRUE
entity_share_client.diff:
- path: '/admin/content/entity_share/pull/diff/{left_revision}/{remote}/{channel_id}/{uuid}'
+ path: '/admin/content/entity_share/pull/diff/{left_revision_id}/{remote}/{channel_id}/{uuid}'
defaults:
_controller: '\Drupal\entity_share_client\Controller\DiffController::compareEntities'
_title: 'Entity comparison'
requirements:
_permission: 'entity_share_client_pull_content'
- _module_dependencies: 'diff'
options:
_admin_route: TRUE
diff --git a/modules/entity_share_client/entity_share_client.services.yml b/modules/entity_share_client/entity_share_client.services.yml
index bd5d504..9aad416 100644
--- a/modules/entity_share_client/entity_share_client.services.yml
+++ b/modules/entity_share_client/entity_share_client.services.yml
@@ -76,3 +76,17 @@ services:
arguments: ['@state']
calls:
- [setKeyRepository, ['@?key.repository']]
+
+ plugin.manager.diff_manager:
+ class: Drupal\entity_share_client\DiffManager
+ parent: default_plugin_manager
+
+ entity_share_client.entity_parser:
+ class: Drupal\entity_share_client\Service\EntityParser
+ arguments:
+ - '@plugin.manager.diff_manager'
+ - '@language_manager'
+ - '@entity_share_client.remote_manager'
+ - '@entity_share_client.jsonapi_helper'
+ - '@jsonapi.resource_type.repository'
+ - '@entity_type.manager'
diff --git a/modules/entity_share_client/src/Annotation/DiffGenerator.php b/modules/entity_share_client/src/Annotation/DiffGenerator.php
new file mode 100644
index 0000000..ad633f1
--- /dev/null
+++ b/modules/entity_share_client/src/Annotation/DiffGenerator.php
@@ -0,0 +1,43 @@
+remoteManager = $container->get('entity_share_client.remote_manager');
- $instance->jsonapiHelper = $container->get('entity_share_client.jsonapi_helper');
- $instance->routeMatch = $container->get('current_route_match');
- $instance->dateFormatter = $container->get('date.formatter');
$instance->resourceTypeRepository = $container->get('jsonapi.resource_type.repository');
+ $instance->diffFormatter = $container->get('diff.formatter');
+ $instance->entityParser = $container->get('entity_share_client.entity_parser');
return $instance;
}
/**
* Returns a table showing the differences between local and remote entities.
*
- * @param int $left_revision
+ * @param int $left_revision_id
* The revision id of the local entity.
* @param \Drupal\entity_share_client\Entity\RemoteInterface $remote
* The remote from which the entity is from.
@@ -80,7 +68,8 @@ class DiffController extends PluginRevisionController {
* @return array
* Table showing the diff between the local and remote entities.
*/
- public function compareEntities($left_revision, RemoteInterface $remote, $channel_id, $uuid) {
+ public function compareEntities(int $left_revision_id, RemoteInterface $remote, $channel_id, $uuid) {
+ $build = [];
// Reload the remote to have config overrides applied.
$remote = $this->entityTypeManager()
->getStorage('remote')
@@ -90,7 +79,12 @@ class DiffController extends PluginRevisionController {
// Get the left/local revision.
$entity_type_id = $channels_infos[$channel_id]['channel_entity_type'];
$storage = $this->entityTypeManager()->getStorage($entity_type_id);
- $left_revision = $storage->loadRevision($left_revision);
+ $left_revision = $storage->loadRevision($left_revision_id);
+
+ $this->entityParser->validateNeedToProcess($left_revision->uuid(), FALSE);
+ $local_values = $this->entityParser->prepareLocalEntity($left_revision);
+
+ $left_yaml = explode("\n", Yaml::encode($local_values));
// Get the right/remote revision.
$url = $channels_infos[$channel_id]['url'];
@@ -99,84 +93,38 @@ class DiffController extends PluginRevisionController {
$response = $this->remoteManager->jsonApiRequest($remote, 'GET', $prepared_url);
$json = Json::decode((string) $response->getBody());
- $entity_type = $storage->getEntityType();
- $entity_keys = $entity_type->getKeys();
+ // There will be only one result.
+ $entity_data = current(EntityShareUtility::prepareData($json['data']));
+ $this->entityParser->validateNeedToProcess($entity_data['id'], TRUE);
+ $remote_values = $this->entityParser->prepareRemoteEntity($entity_data, $remote);
- $resource_type = $this->resourceTypeRepository->get(
- $entity_type_id,
- $left_revision->bundle()
- );
- $id_public_name = $resource_type->getPublicName($entity_keys['id']);
+ $right_yaml = explode("\n", Yaml::encode($remote_values));
- // There will be only one result.
- foreach (EntityShareUtility::prepareData($json['data']) as $entity_data) {
- // Force the remote entity id to be the same as the local entity otherwise
- // the diff is not helpful.
- $entity_data['attributes'][$id_public_name] = $left_revision->id();
- $right_revision = $this->jsonapiHelper->extractEntity($entity_data);
- }
-
- $build = $this->compareEntityRevisions($this->routeMatch, $left_revision, $right_revision, 'split_fields');
+ $build = $this->diffGenerator($left_yaml, $right_yaml);
return $build;
}
/**
- * {@inheritdoc}
+ * Helper.
*/
- public function compareEntityRevisions(RouteMatchInterface $route_match, ContentEntityInterface $left_revision, ContentEntityInterface $right_revision, $filter) {
- $entity = $left_revision;
- // Get language from the entity context.
- $langcode = $entity->language()->getId();
-
- // Get left and right revision in current language.
- $left_revision = $left_revision->getTranslation($langcode);
- $right_revision = $right_revision->getTranslation($langcode);
-
- $build = [
- '#title' => $this->t('Changes to %title', ['%title' => $entity->label()]),
- 'header' => [
- '#prefix' => '',
+ public function diffGenerator(array $left_entity, array $right_entity) {
+ $diff = new Diff($left_entity, $right_entity);
+ $this->diffFormatter->show_header = FALSE;
+ $output = $this->diffFormatter->format($diff);
+ // Add the CSS for the inline diff.
+ $element['#attached']['library'][] = 'system/diff';
+ $element['diff'] = [
+ '#type' => 'table',
+ '#attributes' => [
+ 'class' => ['diff'],
],
- 'controls' => [
- '#prefix' => '
',
- '#suffix' => '
',
+ '#header' => [
+ ['data' => $this->t('Removal'), 'colspan' => '2'],
+ ['data' => $this->t('Additions'), 'colspan' => '2'],
],
+ '#rows' => $output,
];
-
- // Perform comparison only if both entity revisions loaded successfully.
- if ($left_revision != FALSE && $right_revision != FALSE) {
- // Build the diff comparison with the plugin.
- $plugin = $this->diffLayoutManager->createInstance($filter);
- if ($plugin) {
- $build = array_merge_recursive($build, $plugin->build($left_revision, $right_revision, $entity));
- unset($build['header']);
- unset($build['controls']);
-
- // Changes diff table header.
- $left_changed = '';
- if (method_exists($left_revision, 'getChangedTime')) {
- $left_changed = $this->dateFormatter->format($left_revision->getChangedTime(), 'short');
- }
- $build['diff']['#header'][0]['data']['#markup'] = $this->t('Local entity: @changed', [
- '@changed' => $left_changed,
- ]);
- $right_changed = '';
- if (method_exists($right_revision, 'getChangedTime')) {
- $right_changed = $this->dateFormatter->format($right_revision->getChangedTime(), 'short');
- }
- $build['diff']['#header'][1]['data']['#markup'] = $this->t('Remote entity: @changed', [
- '@changed' => $right_changed,
- ]);
-
- $build['diff']['#prefix'] = '';
- $build['diff']['#suffix'] = '
';
- $build['diff']['#attributes']['class'][] = 'diff-responsive-table';
- }
- }
-
- $build['#attached']['library'][] = 'diff/diff.general';
- return $build;
+ return $element;
}
}
diff --git a/modules/entity_share_client/src/DiffGeneratorInterface.php b/modules/entity_share_client/src/DiffGeneratorInterface.php
new file mode 100644
index 0000000..efb3ad0
--- /dev/null
+++ b/modules/entity_share_client/src/DiffGeneratorInterface.php
@@ -0,0 +1,52 @@
+ "This is an example string",
+ * 1 => "Field values or properties",
+ * )
+ * @endcode
+ *
+ * @param \Drupal\Core\Field\FieldItemListInterface $field_items
+ * Represents an entity field.
+ *
+ * @return mixed
+ * An array of strings to be compared. If an empty array is returned it
+ * means that a field is either empty or no properties need to be compared
+ * for that field.
+ * @see \Drupal\entity_share_client\Plugin\diff\CoreFieldDiffParser
+ */
+ public function build(FieldItemListInterface $field_items);
+
+ /**
+ * Returns if the plugin can be used for the provided field.
+ *
+ * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition
+ * The field definition that should be checked.
+ *
+ * @return bool
+ * TRUE if the plugin can be used, FALSE otherwise.
+ */
+ public static function isApplicable(FieldStorageDefinitionInterface $field_definition);
+
+}
diff --git a/modules/entity_share_client/src/DiffManager.php b/modules/entity_share_client/src/DiffManager.php
new file mode 100644
index 0000000..eb36097
--- /dev/null
+++ b/modules/entity_share_client/src/DiffManager.php
@@ -0,0 +1,102 @@
+setCacheBackend($cache_backend, 'field_diff_generator_plugins');
+ }
+
+ /**
+ * Creates a plugin instance for a field definition.
+ *
+ * Creates the instance based on the selected plugin for the field.
+ *
+ * @param string $field_type
+ * The field type.
+ *
+ * @return \Drupal\entity_share_client\DiffGeneratorInterface|null
+ * The plugin instance, NULL if none.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * @throws \Drupal\Component\Plugin\Exception\PluginException
+ */
+ public function createInstanceForFieldDefinition(string $field_type) {
+ if (!isset($this->pluginDefinitions)) {
+ foreach ($this->getDefinitions() as $plugin_definition) {
+ if (isset($plugin_definition['field_types'])) {
+ // Iterate through all the field types this plugin supports
+ // and for every such field type add the id of the plugin.
+ if (!isset($plugin_definition['weight'])) {
+ $plugin_definition['weight'] = 0;
+ }
+
+ foreach ($plugin_definition['field_types'] as $id) {
+ $this->pluginDefinitions[$id][$plugin_definition['id']]['weight'] = $plugin_definition['weight'];
+ }
+ $plugins = $this->pluginDefinitions;
+ }
+ }
+ }
+ else {
+ $plugins = $this->pluginDefinitions;
+ }
+ // Build a list of all diff plugins supporting the field type of the field.
+ $plugin_options = [];
+ if (isset($plugins[$field_type])) {
+ // Sort the plugins based on their weight.
+ uasort($plugins[$field_type], 'Drupal\Component\Utility\SortArray::sortByWeightElement');
+
+ foreach ($plugins[$field_type] as $id => $weight) {
+ $definition = $this->getDefinition($id, FALSE);
+ // Check if the plugin is applicable.
+ if (isset($definition['class']) && in_array($field_type, $definition['field_types'])) {
+ $plugin_options[$id] = $this->getDefinitions()[$id]['label'];
+ }
+ }
+ $settings = key($plugin_options);
+ return $this->createInstance($settings, []);
+ }
+ return NULL;
+ }
+
+}
diff --git a/modules/entity_share_client/src/DiffManagerBase.php b/modules/entity_share_client/src/DiffManagerBase.php
new file mode 100644
index 0000000..27a7310
--- /dev/null
+++ b/modules/entity_share_client/src/DiffManagerBase.php
@@ -0,0 +1,115 @@
+remoteManager = $remoteManager;
+ $this->entityTypeManager = $entity_type_manager;
+ $this->entityParser = $entity_parser;
+ $this->remote = NULL;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_share_client.remote_manager'),
+ $container->get('entity_type.manager'),
+ $container->get('entity_share_client.entity_parser')
+ );
+ }
+
+ /**
+ * Returns Remote entity.
+ */
+ public function getRemote() {
+ return $this->remote;
+ }
+
+ /**
+ * Sets Remote entity.
+ */
+ public function setRemote($remote) {
+ $this->remote = $remote;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function isApplicable(FieldStorageDefinitionInterface $field_definition) {
+ return TRUE;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/CommentFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/CommentFieldDiffParser.php
new file mode 100644
index 0000000..b5f80c8
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/CommentFieldDiffParser.php
@@ -0,0 +1,55 @@
+ $field_item) {
+ if (!$field_item->isEmpty()) {
+ $values = $field_item->getValue();
+
+ // A more human friendly representation.
+ if (isset($values['status'])) {
+ switch ($values['status']) {
+ case CommentItemInterface::OPEN:
+ $result[$field_key] = (string) $this->t('Comments for this entity are open.');
+ break;
+
+ case CommentItemInterface::CLOSED:
+ $result[$field_key] = (string) $this->t('Comments for this entity are closed.');
+ break;
+
+ case CommentItemInterface::HIDDEN:
+ $result[$field_key] = (string) $this->t('Comments for this entity are hidden.');
+ break;
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/CoreFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/CoreFieldDiffParser.php
new file mode 100644
index 0000000..376bb4a
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/CoreFieldDiffParser.php
@@ -0,0 +1,52 @@
+getFieldDefinition();
+ $type = $definition->getType();
+
+ // Every item from $field_items is of type FieldItemInterface.
+ foreach ($field_items as $field_key => $field_item) {
+ if (!$field_item->isEmpty()) {
+ $values = $field_item->getValue();
+ if (isset($values['value'])) {
+ $result[$field_key] = $values['value'];
+ if ($type == 'boolean') {
+ $result[$field_key] = ($result[$field_key] == 1);
+ }
+ // For some reason local numbers are represented as strings,
+ // while remote numbers are indeed numbers. In order to avoid fake
+ // differences, simply cast all numbers to strings.
+ elseif (in_array($type, ['float', 'integer', 'decimal'])) {
+ $result[$field_key] = (string) $result[$field_key];
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/DynamicEntityReferenceFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/DynamicEntityReferenceFieldDiffParser.php
new file mode 100644
index 0000000..f914a83
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/DynamicEntityReferenceFieldDiffParser.php
@@ -0,0 +1,51 @@
+getRemote()) {
+ foreach ($field_items as $field_key => $field_item) {
+ if (!$field_item->isEmpty()) {
+ if ($field_item->entity) {
+ $entity = $field_item->entity;
+ $entity_type_id = $entity->getEntityTypeId();
+ $result[$field_key] = $entity_type_id . ': ' . $entity->uuid();
+ }
+ }
+ }
+ }
+
+ // Case of remote entity.
+ elseif (!empty($remote_field_data['data'])) {
+ foreach ($remote_field_data['data'] as $field_key => $remote_item_data) {
+ list($entity_type_id,) = explode('--', $remote_item_data['type']);
+ $result[$field_key] = $entity_type_id . ': ' . $remote_item_data['id'];
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/EntityReferenceFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/EntityReferenceFieldDiffParser.php
new file mode 100644
index 0000000..44c95f1
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/EntityReferenceFieldDiffParser.php
@@ -0,0 +1,84 @@
+getRemote()) {
+ foreach ($field_items as $field_key => $field_item) {
+ if (!$field_item->isEmpty()) {
+ // Compare entity label.
+ if ($field_item->entity) {
+ $entity = $field_item->entity;
+ // Should we go into recursion and embed the referenced entity?
+ // If the entity has already been processed, don't embed,
+ // to avoid infinite loop.
+ // If the referenced entity type is not Paragraph or Media,
+ // don't embed.
+ if ($this->entityParser->referenceEmbeddable($entity->getEntityTypeId()) &&
+ $this->entityParser->validateNeedToProcess($entity->uuid(), FALSE)) {
+ $result[$field_key] = $this->entityParser->prepareLocalEntity($entity);
+ }
+ // If we are not embedding, just show the referenced entity's UUID.
+ else {
+ $result[$field_key] = $entity->uuid();
+ }
+ }
+ }
+ }
+ }
+
+ // Case of remote entity.
+ elseif (!empty($remote_field_data['data'])) {
+
+ $detailed_response = $this->remoteManager->jsonApiRequest($this->getRemote(), 'GET', $remote_field_data['links']['related']['href']);
+ $entities_json = Json::decode((string) $detailed_response->getBody());
+ $data = $entities_json['data'] ?? [];
+ if (!empty($entities_json['data'])) {
+ $data = EntityShareUtility::prepareData($entities_json['data']);
+ }
+ else {
+ $data = [];
+ }
+
+ foreach ($remote_field_data['data'] as $field_key => $remote_item_data) {
+ $uuid = $data[$field_key]['id'];
+ list($referenced_entity_type,) = explode('--', $remote_item_data['type']);
+ if ($this->entityParser->referenceEmbeddable($referenced_entity_type) &&
+ $this->entityParser->validateNeedToProcess($uuid, TRUE)) {
+ $result[$field_key] = $this->entityParser->prepareRemoteEntity($data[$field_key], $this->getRemote());
+ }
+ else {
+ $result[$field_key] = $remote_item_data['id'];
+ }
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/EntityReferenceRevisionsFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/EntityReferenceRevisionsFieldDiffParser.php
new file mode 100644
index 0000000..0faaf2a
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/EntityReferenceRevisionsFieldDiffParser.php
@@ -0,0 +1,17 @@
+entityTypeManager->getStorage('file');
+
+ // Every item from $field_items is of type FieldItemInterface.
+ if (!$this->getRemote()) {
+ foreach ($field_items as $field_key => $field_item) {
+ // Even though the local field is empty, remote data may be set.
+ // So, get field values regardless.
+ $values = $field_item->getValue();
+
+ if (!$field_item->isEmpty()) {
+ // Compare file names.
+ if (isset($values['target_id'])) {
+ /** @var \Drupal\file\Entity\File $file */
+ $file = $fileManager->load($values['target_id']);
+ if ($file instanceof File) {
+ $label = (string) $this->t('File name');
+ $result[$field_key][$label] = $file->getFilename();
+ $label = (string) $this->t('File size');
+ $result[$field_key][$label] = $file->getSize();
+ }
+ }
+ }
+ // Compare additional (meta) fields.
+ foreach ($this->getFieldMetaProperties() as $key => $label) {
+ if (isset($values[$key])) {
+ $label = $label;
+ $result[$field_key][$label] = $values[$key];
+ }
+ }
+ }
+ }
+ elseif (!empty($remote_field_data['data'])) {
+
+ $detailed_response = $this->remoteManager->jsonApiRequest($this->getRemote(), 'GET', $remote_field_data['links']['related']['href']);
+ $entities_json = Json::decode((string) $detailed_response->getBody());
+ $data = $entities_json['data'] ?? [];
+ if (!empty($entities_json['data'])) {
+ $data = EntityShareUtility::prepareData($entities_json['data']);
+ }
+ else {
+ $data = [];
+ }
+
+ foreach ($remote_field_data['data'] as $field_key => $remote_item_data) {
+ if ($data[$field_key]['attributes']['filename']) {
+ $label = (string) $this->t('File name');
+ $result[$field_key][$label] = $data[$field_key]['attributes']['filename'];
+ $label = (string) $this->t('File size');
+ $result[$field_key][$label] = (string) $data[$field_key]['attributes']['filesize'];
+ }
+ // Compare additional (meta) fields.
+ foreach ($this->getFieldMetaProperties() as $key => $label) {
+ if (isset($remote_field_data['data'][$field_key]['meta'][$key])) {
+ $result[$field_key][$label] = $remote_field_data['data'][$field_key]['meta'][$key];
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Declares needed field meta properties.
+ */
+ protected function getFieldMetaProperties() {
+ return [
+ 'description' => (string) $this->t('Description'),
+ ];
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/ImageFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/ImageFieldDiffParser.php
new file mode 100644
index 0000000..909161e
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/ImageFieldDiffParser.php
@@ -0,0 +1,28 @@
+ (string) $this->t('Alt'),
+ 'title' => (string) $this->t('Image title'),
+ ];
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/LinkFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/LinkFieldDiffParser.php
new file mode 100644
index 0000000..1dd9d70
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/LinkFieldDiffParser.php
@@ -0,0 +1,47 @@
+ $field_item) {
+ if (!$field_item->isEmpty()) {
+ $values = $field_item->getValue();
+ // Compare the link title.
+ if (isset($values['title'])) {
+ $label = (string) $this->t('Title');
+ $result[$field_key][$label] = $values['title'];
+ }
+ // Compare the uri if exists.
+ if (isset($values['uri'])) {
+ $label = (string) $this->t('URL');
+ $result[$field_key][$label] = $values['uri'];
+ }
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/ListFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/ListFieldDiffParser.php
new file mode 100644
index 0000000..cbd5d39
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/ListFieldDiffParser.php
@@ -0,0 +1,47 @@
+ $field_item) {
+ // Build the array for comparison only if the field is not empty.
+ if (!$field_item->isEmpty()) {
+ $possible_options = $field_item->getPossibleOptions();
+ $values = $field_item->getValue();
+ if ($possible_options) {
+ $result[$field_key] = $possible_options[$values['value']] . ' (' . $values['value'] . ')';
+ }
+ else {
+ $result[$field_key] = $possible_options[$values['value']];
+ }
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/TextFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/TextFieldDiffParser.php
new file mode 100644
index 0000000..e2f8744
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/TextFieldDiffParser.php
@@ -0,0 +1,41 @@
+ $field_item) {
+ $values = $field_item->getValue();
+ // Compare field values.
+ if (isset($values['value'])) {
+ // Check if summary or text format are included in the diff.
+ $label = (string) $this->t('Value');
+ $result[$field_key][$label] = $values['value'];
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Plugin/diff/TextWithSummaryFieldDiffParser.php b/modules/entity_share_client/src/Plugin/diff/TextWithSummaryFieldDiffParser.php
new file mode 100644
index 0000000..a413d80
--- /dev/null
+++ b/modules/entity_share_client/src/Plugin/diff/TextWithSummaryFieldDiffParser.php
@@ -0,0 +1,48 @@
+ $field_item) {
+ $values = $field_item->getValue();
+ // Handle the text summary.
+ if (isset($values['summary'])) {
+ if ($values['summary'] != "") {
+ $label = (string) $this->t('Summary');
+ $result[$field_key][$label] = $values['summary'];
+ }
+ }
+
+ // Compare field values.
+ if (isset($values['value'])) {
+ // Check if summary or text format are included in the diff.
+ $label = (string) $this->t('Value');
+ $result[$field_key][$label] = $values['value'];
+ }
+ }
+
+ return $result;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Service/EntityParser.php b/modules/entity_share_client/src/Service/EntityParser.php
new file mode 100644
index 0000000..53586ee
--- /dev/null
+++ b/modules/entity_share_client/src/Service/EntityParser.php
@@ -0,0 +1,360 @@
+diffManager = $diff_manager;
+ $this->languageManager = $language_manager;
+ $this->remoteManager = $remote_manager;
+ $this->jsonapiHelper = $jsonapi_helper;
+ $this->resourceTypeRepository = $resource_type_repository;
+ $this->entityTypeManager = $entity_type_manager;
+ $this->entityDefinitions = $entity_type_manager->getDefinitions();
+ $this->processedEntities = [
+ 'local' => [],
+ 'remote' => [],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepareLocalEntity(ContentEntityInterface $entity) {
+ return $this->parseEntity($entity);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prepareRemoteEntity(array $remote_data, RemoteInterface $remote) {
+ $remote_entity = $this->jsonapiHelper->extractEntity($remote_data);
+ return $this->parseEntity($remote_entity, $remote_data, $remote, TRUE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateNeedToProcess(string $uuid, bool $remote) {
+ $main_key = $remote ? 'remote' : 'local';
+ if (!in_array($uuid, $this->processedEntities[$main_key])) {
+ $this->processedEntities[$main_key][] = $uuid;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Parses an entity.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The Drupal entity (local or remote).
+ * @param array $remote_data
+ * Used for remote entity: entity data coming from JSON:API.
+ * @param \Drupal\entity_share_client\Entity\RemoteInterface $remote
+ * Used for remote entity: The ES Remote entity.
+ * @param bool $from_server
+ * Whether the entity is remote or local.
+ *
+ * @return array
+ * Parsed data of a field, suitable for YAML parsing.
+ * Associative array, keyed by labels.
+ * Values are strings, numbers or arrays.
+ */
+ protected function parseEntity(ContentEntityInterface $entity, array $remote_data = NULL, RemoteInterface $remote = NULL, bool $from_server = FALSE) {
+ $result = [];
+ $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
+ if ($from_server) {
+ $resource_type = $this->getResourceType($entity);
+ }
+ // Load entity of current language, otherwise fields are always compared by
+ // their default language.
+ if ($entity->hasTranslation($langcode)) {
+ $entity = $entity->getTranslation($langcode);
+ }
+ $irrelevant_fields = $this->getFieldsIrrelevantForDiff($entity);
+ // Loop through entity fields and transform every FieldItemList object
+ // into an array of strings according to field type specific settings.
+ /** @var \Drupal\Core\Field\FieldItemListInterface $field_items */
+ foreach ($entity as $item_key => $field_items) {
+ // Prepare remote information for reference fields, if exists.
+ $public_key = $from_server ? $resource_type->getPublicName($item_key) : $item_key;
+ $remote_field_data = [];
+ // If a field is an entity reference, then eliminate it in
+ // case the relationship is not handleable.
+ if ($field_items instanceof EntityReferenceFieldItemListInterface) {
+ $field_settings = $field_items->getSettings();
+ // Determine the referenced entity types by this field.
+ if (isset($field_settings['target_type'])) {
+ $referenced_types = [$field_settings['target_type']];
+ }
+ // Dynamic entity reference.
+ elseif (isset($field_settings['entity_type_ids'])) {
+ $referenced_types = $field_settings['entity_type_ids'];
+ }
+ else {
+ $referenced_types = [];
+ }
+ if ($this->referencedTypesHandlable($referenced_types)) {
+ $should_parse = TRUE;
+ if (isset($remote_data['relationships'][$public_key])) {
+ $remote_field_data = $remote_data['relationships'][$public_key];
+ if (isset($remote_field_data['data'])) {
+ $remote_field_data['data'] = EntityShareUtility::prepareData($remote_field_data['data']);
+ }
+ }
+ }
+ else {
+ $should_parse = FALSE;
+ }
+ }
+ else {
+ $should_parse = !in_array($item_key, $irrelevant_fields);
+ }
+ if (!$should_parse) {
+ continue;
+ }
+ $parsed_field = $this->parseField($item_key, $field_items, $remote_field_data, $remote, $from_server);
+ if ($parsed_field != NULL) {
+ $field_label = (string) $field_items->getFieldDefinition()->getLabel();
+ $result[$field_label] = $parsed_field;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Parses a field or property of entity.
+ *
+ * @param string $item_key
+ * The field key (machine name).
+ * @param \Drupal\Core\Field\FieldItemListInterface $field_items
+ * Field items.
+ * @param array $remote_field_data
+ * Used for remote entity: field data coming from JSON:API.
+ * @param \Drupal\entity_share_client\Entity\RemoteInterface $remote
+ * Used for remote entity: The ES Remote entity.
+ * @param bool $from_server
+ * Whether the entity is remote or local.
+ *
+ * @return array
+ * Parsed data of a field, suitable for YAML parsing.
+ */
+ protected function parseField(string $item_key, FieldItemListInterface $field_items, array $remote_field_data = [], RemoteInterface $remote = NULL, bool $from_server = FALSE) {
+ $build = [];
+ $field_type = $field_items->getFieldDefinition()->getType();
+ $plugin = $this->diffManager->createInstanceForFieldDefinition($field_type);
+ if ($plugin) {
+ if ($from_server) {
+ $plugin->setRemote($remote);
+ }
+ $build = $plugin->build($field_items, $remote_field_data);
+ if (!empty($build)) {
+ $cardinality = $field_items->getFieldDefinition()->getFieldStorageDefinition()->getCardinality();
+ if ($cardinality == 1 && is_array($build)) {
+ $build = current($build);
+ }
+ }
+ }
+ return $build;
+ }
+
+ /**
+ * Gets a specific JSON:API resource type based on entity type ID and bundle.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The Drupal entity (local or remote).
+ *
+ * @return \Drupal\jsonapi\ResourceType\ResourceType
+ * The requested JSON:API resource type, if it exists. NULL otherwise.
+ */
+ public function getResourceType(ContentEntityInterface $entity) {
+ $resource_type = $this->resourceTypeRepository->get(
+ $entity->getEntityTypeId(),
+ $entity->bundle()
+ );
+ return $resource_type;
+ }
+
+ /**
+ * Checks if the entity should be embedded into Diff or just listed with UUID.
+ *
+ * @param string $entity_type_id
+ * Entity type ID.
+ *
+ * @return bool
+ * Whether entity of this type is embeddable or not.
+ */
+ public function referenceEmbeddable(string $entity_type_id) {
+ $embeddable_types = [
+ 'paragraph',
+ 'media',
+ ];
+ return in_array($entity_type_id, $embeddable_types);
+ }
+
+ /**
+ * Checks if references to this entity type can be handled or just skipped.
+ *
+ * @param string[] $entity_type_ids
+ * Array of entity type IDs.
+ *
+ * @return bool
+ * Whether references to this entity type can be handled.
+ */
+ public function referencedTypesHandlable(array $entity_type_ids) {
+ if (empty($entity_type_ids)) {
+ return FALSE;
+ }
+ // All referenced entity types must be supported.
+ foreach ($entity_type_ids as $entity_type_id) {
+ if ($entity_type_id == 'user') {
+ return FALSE;
+ }
+ if ($this->entityDefinitions[$entity_type_id]->getGroup() == 'configuration') {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+
+ /**
+ * Helper: lists entity properties/fields which should not appear in Diff.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The Drupal entity (local or remote).
+ *
+ * @return string[]
+ * Array of entity properties/fields.
+ */
+ protected function getFieldsIrrelevantForDiff(ContentEntityInterface $entity) {
+ $field_names = [];
+ // Entity keys.
+ $entity_keys = $entity->getEntityType()->getKeys();
+ // Label and language code should be displayed in the Diff.
+ unset($entity_keys['label']);
+ unset($entity_keys['langcode']);
+ $field_names = array_values($entity_keys);
+ // Revision keys.
+ $revision_keys = array_keys($entity->getEntityType()->getRevisionMetadataKeys());
+ $field_names = array_merge($field_names, $revision_keys);
+ // Other keys.
+ $other_keys = [
+ 'changed',
+ 'created',
+ // Related to translation.
+ 'content_translation_source',
+ 'content_translation_affected',
+ 'content_translation_outdated',
+ 'revision_translation_affected',
+ // Related to paragraphs.
+ 'parent_id',
+ 'parent_type',
+ 'parent_field_name',
+ // For some reason getRevisionMetadataKeys() doesn't always return these.
+ 'revision_timestamp',
+ 'revision_log',
+ ];
+ $field_names = array_merge($field_names, $other_keys);
+ return $field_names;
+ }
+
+}
diff --git a/modules/entity_share_client/src/Service/EntityParserInterface.php b/modules/entity_share_client/src/Service/EntityParserInterface.php
new file mode 100644
index 0000000..c92a03b
--- /dev/null
+++ b/modules/entity_share_client/src/Service/EntityParserInterface.php
@@ -0,0 +1,30 @@
+getPublicName($entity_keys['id']);
- if ($this->moduleHandler->moduleExists('diff') &&
+ if (
in_array($status_info['info_id'], [
StateInformationInterface::INFO_ID_CHANGED,
StateInformationInterface::INFO_ID_NEW_TRANSLATION,
@@ -204,7 +204,7 @@ class FormHelper implements FormHelperInterface {
$options[$data['id']]['status']['data'] = new FormattableMarkup('@label: @diff_link', [
'@label' => $options[$data['id']]['status']['data'],
'@diff_link' => Link::createFromRoute($this->t('Diff'), 'entity_share_client.diff', [
- 'left_revision' => $status_info['local_revision_id'],
+ 'left_revision_id' => $status_info['local_revision_id'],
'remote' => $remote->id(),
'channel_id' => $channel_id,
'uuid' => $data['id'],