Version 1
[yaffs-website] / web / modules / contrib / diff / src / DiffEntityComparison.php
1 <?php
2
3 namespace Drupal\diff;
4
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;
11
12 /**
13  * Entity comparison service that prepares a diff of a pair of entities.
14  */
15 class DiffEntityComparison {
16
17   /**
18    * Contains the configuration object factory.
19    *
20    * @var \Drupal\Core\Config\ConfigFactoryInterface
21    */
22   protected $configFactory;
23
24   /**
25    * Wrapper object for simple configuration from diff.plugins.yml.
26    *
27    * @var \Drupal\Core\Config\ImmutableConfig
28    */
29   protected $pluginsConfig;
30
31   /**
32    * The diff formatter.
33    *
34    * @var \Drupal\Core\Diff\DiffFormatter
35    */
36   protected $diffFormatter;
37
38   /**
39    * A list of all the field types from the system and their definitions.
40    *
41    * @var array
42    */
43   protected $fieldTypeDefinitions;
44
45   /**
46    * The entity parser.
47    *
48    * @var \Drupal\diff\DiffEntityParser
49    */
50   protected $entityParser;
51
52   /**
53    * The field diff plugin manager service.
54    *
55    * @var \Drupal\diff\DiffBuilderManager
56    */
57   protected $diffBuilderManager;
58
59   /**
60    * Constructs a DiffEntityComparison object.
61    *
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
69    *   The entity parser.
70    * @param \Drupal\diff\DiffBuilderManager $diff_builder_manager
71    *   The diff builder manager.
72    */
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;
80   }
81
82   /**
83    * This method should return an array of items ready to be compared.
84    *
85    * @param \Drupal\Core\Entity\ContentEntityInterface $left_entity
86    *   The left entity.
87    * @param \Drupal\Core\Entity\ContentEntityInterface $right_entity
88    *   The right entity.
89    *
90    * @return array
91    *   Items ready to be compared by the Diff component.
92    */
93   public function compareRevisions(ContentEntityInterface $left_entity, ContentEntityInterface $right_entity) {
94     $result = array();
95
96     $left_values = $this->entityParser->parseEntity($left_entity);
97     $right_values = $this->entityParser->parseEntity($right_entity);
98
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,
106         '#data' => [],
107       ];
108
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]);
115       }
116       // This field exists only on the left entity.
117       else {
118         $result[$left_key]['#data'] += $this->combineFields($left_values[$left_key], []);
119       }
120     }
121
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,
129         '#data' => [],
130       ];
131       $result[$right_key]['#data'] += $this->combineFields([], $right_values[$right_key]);
132     }
133
134     return $result;
135   }
136
137   /**
138    * Combine two fields into an array with keys '#left' and '#right'.
139    *
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.
144    *
145    * @return array
146    *   Array resulted after combining the left and right values.
147    */
148   protected function combineFields(array $left_values, array $right_values) {
149     $result = array(
150       '#left' => array(),
151       '#right' => array(),
152     );
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'];
162         }
163         else {
164           $result['#left'][] = is_array($value) ? implode("\n", $value) : $value;
165         }
166       }
167       if (isset($right_values[$delta])) {
168         $value = $right_values[$delta];
169         if (isset($value['#thumbnail'])) {
170           $result['#right_thumbnail'][] = $value['#thumbnail'];
171         }
172         else {
173           $result['#right'][] = is_array($value) ? implode("\n", $value) : $value;
174         }
175       }
176     }
177
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']);
181
182     return $result;
183   }
184
185   /**
186    * Prepare the table rows for #type 'table'.
187    *
188    * @param string $a
189    *   The source string to compare from.
190    * @param string $b
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.
196    *
197    * @see \Drupal\Component\Diff\DiffFormatter::format
198    *
199    * @return array
200    *   Array of rows usable with #type => 'table' returned by the core diff
201    *   formatter when format a diff.
202    */
203   public function getRows($a, $b, $show_header = FALSE, array &$line_stats = NULL) {
204     if (!isset($line_stats)) {
205       $line_stats = array(
206         'counter' => array('x' => 0, 'y' => 0),
207         'offset' => array('x' => 0, 'y' => 0),
208       );
209     }
210
211     // Header is the line counter.
212     $this->diffFormatter->show_header = $show_header;
213     $diff = new Diff($a, $b);
214
215     return $this->diffFormatter->format($diff);
216   }
217
218   /**
219    * Splits the strings into lines and counts the resulted number of lines.
220    *
221    * @param array $diff
222    *   Array of strings.
223    */
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']);
229       }
230       $diff['#data']['#count_left'] = count($diff['#data']['#left']);
231     }
232     else {
233       $diff['#data']['#count_left'] = 0;
234       $diff['#data']['#left'] = [];
235     }
236     if (isset($data['#right']) && $data['#right'] != '') {
237       if (is_string($data['#right'])) {
238         $diff['#data']['#right'] = explode("\n", $data['#right']);
239       }
240       $diff['#data']['#count_right'] = count($diff['#data']['#right']);
241     }
242     else {
243       $diff['#data']['#count_right'] = 0;
244       $diff['#data']['#right'] = [];
245     }
246   }
247
248   /**
249    * Gets the revision description of the revision.
250    *
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.
255    *
256    * @return string
257    *   The revision log message.
258    */
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());
265     }
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'];
279             }
280             unset($left_values[$key]);
281           }
282           else {
283             $summary_elements[] = $value['label'];
284           }
285         }
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'];
290           }
291         }
292         if (count($summary_elements) > 0) {
293           $revision_summary = 'Changes on: ' . implode(', ', $summary_elements);
294         }
295         else {
296           $revision_summary = 'No changes.';
297         }
298       }
299       else {
300         $revision_summary = 'Initial revision.';
301       }
302     }
303
304     return $revision_summary;
305   }
306
307   /**
308    * Creates an log message based on the changes of entity fields.
309    *
310    * @param \Drupal\Core\Entity\ContentEntityInterface $revision
311    *   The current revision.
312    *
313    * @return array
314    *   Array of the revision fields with their value and label.
315    */
316   protected function summary(ContentEntityInterface $revision) {
317     $result = [];
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) {
323       $show_delta = FALSE;
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) {
333                 $show_delta = TRUE;
334               }
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'];
338             };
339           }
340         }
341         else {
342           // Create a unique flat key.
343           $key = $revision->id() . ':' . $entity_type_id . '.' . $field_items->getName();
344
345           $result[$key]['value'] = $field_items->getValue();
346           $result[$key]['label'] = $field_items->getFieldDefinition()->getLabel();
347         }
348       }
349     }
350
351     return $result;
352   }
353
354 }