5 use Drupal\Core\Config\ConfigFactory;
6 use Drupal\Component\Plugin\PluginManagerInterface;
7 use Drupal\Core\Entity\ContentEntityInterface;
8 use Drupal\Component\Diff\Diff;
9 use Drupal\Core\Entity\RevisionLogInterface;
10 use Drupal\Component\Utility\Xss;
13 * Entity comparison service that prepares a diff of a pair of entities.
15 class DiffEntityComparison {
18 * Contains the configuration object factory.
20 * @var \Drupal\Core\Config\ConfigFactoryInterface
22 protected $configFactory;
25 * Wrapper object for simple configuration from diff.plugins.yml.
27 * @var \Drupal\Core\Config\ImmutableConfig
29 protected $pluginsConfig;
34 * @var \Drupal\Core\Diff\DiffFormatter
36 protected $diffFormatter;
39 * A list of all the field types from the system and their definitions.
43 protected $fieldTypeDefinitions;
48 * @var \Drupal\diff\DiffEntityParser
50 protected $entityParser;
53 * The field diff plugin manager service.
55 * @var \Drupal\diff\DiffBuilderManager
57 protected $diffBuilderManager;
60 * Constructs a DiffEntityComparison object.
62 * @param \Drupal\Core\Config\ConfigFactory $config_factory
63 * The configuration factory.
64 * @param \Drupal\diff\DiffFormatter $diff_formatter
65 * The diff formatter service.
66 * @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager
67 * The plugin manager service.
68 * @param \Drupal\diff\DiffEntityParser $entity_parser
70 * @param \Drupal\diff\DiffBuilderManager $diff_builder_manager
71 * The diff builder manager.
73 public function __construct(ConfigFactory $config_factory, DiffFormatter $diff_formatter, PluginManagerInterface $plugin_manager, DiffEntityParser $entity_parser, DiffBuilderManager $diff_builder_manager) {
74 $this->configFactory = $config_factory;
75 $this->pluginsConfig = $this->configFactory->get('diff.plugins');
76 $this->diffFormatter = $diff_formatter;
77 $this->fieldTypeDefinitions = $plugin_manager->getDefinitions();
78 $this->entityParser = $entity_parser;
79 $this->diffBuilderManager = $diff_builder_manager;
83 * This method should return an array of items ready to be compared.
85 * @param \Drupal\Core\Entity\ContentEntityInterface $left_entity
87 * @param \Drupal\Core\Entity\ContentEntityInterface $right_entity
91 * Items ready to be compared by the Diff component.
93 public function compareRevisions(ContentEntityInterface $left_entity, ContentEntityInterface $right_entity) {
96 $left_values = $this->entityParser->parseEntity($left_entity);
97 $right_values = $this->entityParser->parseEntity($right_entity);
99 foreach ($left_values as $left_key => $values) {
100 list (, $field_key) = explode(':', $left_key);
101 // Get the compare settings for this field type.
102 $compare_settings = $this->pluginsConfig->get('fields.' . $field_key);
103 $result[$left_key] = [
104 '#name' => (isset($compare_settings['settings']['show_header']) && $compare_settings['settings']['show_header'] == 0) ? '' : $values['label'],
105 '#settings' => $compare_settings,
109 // Fields which exist on the right entity also.
110 if (isset($right_values[$left_key])) {
111 $result[$left_key]['#data'] += $this->combineFields($left_values[$left_key], $right_values[$left_key]);
112 // Unset the field from the right entity so that we know if the right
113 // entity has any fields that left entity doesn't have.
114 unset($right_values[$left_key]);
116 // This field exists only on the left entity.
118 $result[$left_key]['#data'] += $this->combineFields($left_values[$left_key], []);
122 // Fields which exist only on the right entity.
123 foreach ($right_values as $right_key => $values) {
124 list (, $field_key) = explode(':', $right_key);
125 $compare_settings = $this->pluginsConfig->get('fields.' . $field_key);
126 $result[$right_key] = [
127 '#name' => (isset($compare_settings['settings']['show_header']) && $compare_settings['settings']['show_header'] == 0) ? '' : $values['label'],
128 '#settings' => $compare_settings,
131 $result[$right_key]['#data'] += $this->combineFields([], $right_values[$right_key]);
138 * Combine two fields into an array with keys '#left' and '#right'.
140 * @param array $left_values
141 * Entity field formatted into an array of strings.
142 * @param array $right_values
143 * Entity field formatted into an array of strings.
146 * Array resulted after combining the left and right values.
148 protected function combineFields(array $left_values, array $right_values) {
153 $max = max(array(count($left_values), count($right_values)));
154 for ($delta = 0; $delta < $max; $delta++) {
155 // EXPERIMENTAL: Transform thumbnail from ImageFieldBuilder.
156 // @todo Make thumbnail / rich diff data pluggable.
157 // @see https://www.drupal.org/node/2840566
158 if (isset($left_values[$delta])) {
159 $value = $left_values[$delta];
160 if (isset($value['#thumbnail'])) {
161 $result['#left_thumbnail'][] = $value['#thumbnail'];
164 $result['#left'][] = is_array($value) ? implode("\n", $value) : $value;
167 if (isset($right_values[$delta])) {
168 $value = $right_values[$delta];
169 if (isset($value['#thumbnail'])) {
170 $result['#right_thumbnail'][] = $value['#thumbnail'];
173 $result['#right'][] = is_array($value) ? implode("\n", $value) : $value;
178 // If a field has multiple values combine them into one single string.
179 $result['#left'] = implode("\n", $result['#left']);
180 $result['#right'] = implode("\n", $result['#right']);
186 * Prepare the table rows for #type 'table'.
189 * The source string to compare from.
191 * The target string to compare to.
192 * @param bool $show_header
193 * Display diff context headers. For example, "Line x".
194 * @param array $line_stats
195 * Tracks line numbers across multiple calls to DiffFormatter.
197 * @see \Drupal\Component\Diff\DiffFormatter::format
200 * Array of rows usable with #type => 'table' returned by the core diff
201 * formatter when format a diff.
203 public function getRows($a, $b, $show_header = FALSE, array &$line_stats = NULL) {
204 if (!isset($line_stats)) {
206 'counter' => array('x' => 0, 'y' => 0),
207 'offset' => array('x' => 0, 'y' => 0),
211 // Header is the line counter.
212 $this->diffFormatter->show_header = $show_header;
213 $diff = new Diff($a, $b);
215 return $this->diffFormatter->format($diff);
219 * Splits the strings into lines and counts the resulted number of lines.
224 public function processStateLine(array &$diff) {
225 $data = $diff['#data'];
226 if (isset($data['#left']) && $data['#left'] != '') {
227 if (is_string($data['#left'])) {
228 $diff['#data']['#left'] = explode("\n", $data['#left']);
230 $diff['#data']['#count_left'] = count($diff['#data']['#left']);
233 $diff['#data']['#count_left'] = 0;
234 $diff['#data']['#left'] = [];
236 if (isset($data['#right']) && $data['#right'] != '') {
237 if (is_string($data['#right'])) {
238 $diff['#data']['#right'] = explode("\n", $data['#right']);
240 $diff['#data']['#count_right'] = count($diff['#data']['#right']);
243 $diff['#data']['#count_right'] = 0;
244 $diff['#data']['#right'] = [];
249 * Gets the revision description of the revision.
251 * @param \Drupal\Core\Entity\ContentEntityInterface $revision
252 * The current revision.
253 * @param \Drupal\Core\Entity\ContentEntityInterface $previous_revision
254 * (optional) The previous revision. Defaults to NULL.
257 * The revision log message.
259 public function getRevisionDescription(ContentEntityInterface $revision, ContentEntityInterface $previous_revision = NULL) {
260 $summary_elements = [];
261 $revision_summary = '';
262 // Check if the revision has a revision log message.
263 if ($revision instanceof RevisionLogInterface) {
264 $revision_summary = Xss::filter($revision->getRevisionLogMessage());
266 // Auto generate the revision log.
267 if ($revision_summary == '') {
268 // If there is a previous revision, load values of both revisions, loop
269 // over the current revision fields.
270 if ($previous_revision) {
271 $left_values = $this->summary($previous_revision);
272 $right_values = $this->summary($revision);
273 foreach ($right_values as $key => $value) {
274 // Unset left values after comparing. Add right value label to the
275 // summary if it is changed or new.
276 if (isset($left_values[$key])) {
277 if ($value['value'] != $left_values[$key]['value']) {
278 $summary_elements[] = $value['label'];
280 unset($left_values[$key]);
283 $summary_elements[] = $value['label'];
286 // Add the remaining left values if not present in the right entity.
287 foreach ($left_values as $key => $value) {
288 if (!isset($right_values[$key])) {
289 $summary_elements[] = $value['label'];
292 if (count($summary_elements) > 0) {
293 $revision_summary = 'Changes on: ' . implode(', ', $summary_elements);
296 $revision_summary = 'No changes.';
300 $revision_summary = 'Initial revision.';
304 return $revision_summary;
308 * Creates an log message based on the changes of entity fields.
310 * @param \Drupal\Core\Entity\ContentEntityInterface $revision
311 * The current revision.
314 * Array of the revision fields with their value and label.
316 protected function summary(ContentEntityInterface $revision) {
318 $entity_type_id = $revision->getEntityTypeId();
319 // Loop through entity fields and transform every FieldItemList object
320 // into an array of strings according to field type specific settings.
321 /** @var \Drupal\Core\Field\FieldItemListInterface $field_items */
322 foreach ($revision as $field_items) {
324 // Create a plugin instance for the field definition.
325 $plugin = $this->diffBuilderManager->createInstanceForFieldDefinition($field_items->getFieldDefinition());
326 if ($plugin && $this->diffBuilderManager->showDiff($field_items->getFieldDefinition()->getFieldStorageDefinition())) {
327 // Create the array with the fields of the entity. Recursive if the
328 // field contains entities.
329 if ($plugin instanceof FieldReferenceInterface) {
330 foreach ($plugin->getEntitiesToDiff($field_items) as $entity_key => $reference_entity) {
331 foreach ($this->summary($reference_entity) as $key => $build) {
332 if ($field_items->getFieldDefinition()->getFieldStorageDefinition()->getCardinality() != 1) {
335 $result[$key] = $build;
336 $delta = $show_delta ? '<sub>' . ($entity_key + 1) . '</sub> ' : ' - ';
337 $result[$key]['label'] = $field_items->getFieldDefinition()->getLabel() . $delta . $result[$key]['label'];
342 // Create a unique flat key.
343 $key = $revision->id() . ':' . $entity_type_id . '.' . $field_items->getName();
345 $result[$key]['value'] = $field_items->getValue();
346 $result[$key]['label'] = $field_items->getFieldDefinition()->getLabel();