Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / diff / src / DiffEntityComparison.php
1 <?php
2
3 namespace Drupal\diff;
4
5 use Drupal\content_moderation\ModerationInformationInterface;
6 use Drupal\Core\Config\ConfigFactory;
7 use Drupal\Component\Plugin\PluginManagerInterface;
8 use Drupal\Core\Entity\ContentEntityInterface;
9 use Drupal\Component\Diff\Diff;
10 use Drupal\Core\Entity\RevisionLogInterface;
11 use Drupal\Component\Utility\Xss;
12
13 /**
14  * Entity comparison service that prepares a diff of a pair of entities.
15  */
16 class DiffEntityComparison {
17
18   /**
19    * Contains the configuration object factory.
20    *
21    * @var \Drupal\Core\Config\ConfigFactoryInterface
22    */
23   protected $configFactory;
24
25   /**
26    * Wrapper object for simple configuration from diff.plugins.yml.
27    *
28    * @var \Drupal\Core\Config\ImmutableConfig
29    */
30   protected $pluginsConfig;
31
32   /**
33    * The diff formatter.
34    *
35    * @var \Drupal\Core\Diff\DiffFormatter
36    */
37   protected $diffFormatter;
38
39   /**
40    * A list of all the field types from the system and their definitions.
41    *
42    * @var array
43    */
44   protected $fieldTypeDefinitions;
45
46   /**
47    * The entity parser.
48    *
49    * @var \Drupal\diff\DiffEntityParser
50    */
51   protected $entityParser;
52
53   /**
54    * The field diff plugin manager service.
55    *
56    * @var \Drupal\diff\DiffBuilderManager
57    */
58   protected $diffBuilderManager;
59
60   /**
61    * The content moderation service, if available.
62    *
63    * @var \Drupal\content_moderation\ModerationInformationInterface
64    */
65   protected $moderationInformation;
66
67   /**
68    * Constructs a DiffEntityComparison object.
69    *
70    * @param \Drupal\Core\Config\ConfigFactory $config_factory
71    *   The configuration factory.
72    * @param \Drupal\diff\DiffFormatter $diff_formatter
73    *   The diff formatter service.
74    * @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager
75    *   The plugin manager service.
76    * @param \Drupal\diff\DiffEntityParser $entity_parser
77    *   The entity parser.
78    * @param \Drupal\diff\DiffBuilderManager $diff_builder_manager
79    *   The diff builder manager.
80    */
81   public function __construct(ConfigFactory $config_factory, DiffFormatter $diff_formatter, PluginManagerInterface $plugin_manager, DiffEntityParser $entity_parser, DiffBuilderManager $diff_builder_manager) {
82     $this->configFactory = $config_factory;
83     $this->pluginsConfig = $this->configFactory->get('diff.plugins');
84     $this->diffFormatter = $diff_formatter;
85     $this->fieldTypeDefinitions = $plugin_manager->getDefinitions();
86     $this->entityParser = $entity_parser;
87     $this->diffBuilderManager = $diff_builder_manager;
88   }
89
90   /**
91    * This method should return an array of items ready to be compared.
92    *
93    * @param \Drupal\Core\Entity\ContentEntityInterface $left_entity
94    *   The left entity.
95    * @param \Drupal\Core\Entity\ContentEntityInterface $right_entity
96    *   The right entity.
97    *
98    * @return array
99    *   Items ready to be compared by the Diff component.
100    */
101   public function compareRevisions(ContentEntityInterface $left_entity, ContentEntityInterface $right_entity) {
102     $result = array();
103
104     $left_values = $this->entityParser->parseEntity($left_entity);
105     $right_values = $this->entityParser->parseEntity($right_entity);
106
107     foreach ($left_values as $left_key => $values) {
108       list (, $field_key) = explode(':', $left_key);
109       // Get the compare settings for this field type.
110       $compare_settings = $this->pluginsConfig->get('fields.' . $field_key);
111       $result[$left_key] = [
112         '#name' => (isset($compare_settings['settings']['show_header']) && $compare_settings['settings']['show_header'] == 0) ? '' : $values['label'],
113         '#settings' => $compare_settings,
114         '#data' => [],
115       ];
116
117       // Fields which exist on the right entity also.
118       if (isset($right_values[$left_key])) {
119         $result[$left_key]['#data'] += $this->combineFields($left_values[$left_key], $right_values[$left_key]);
120         // Unset the field from the right entity so that we know if the right
121         // entity has any fields that left entity doesn't have.
122         unset($right_values[$left_key]);
123       }
124       // This field exists only on the left entity.
125       else {
126         $result[$left_key]['#data'] += $this->combineFields($left_values[$left_key], []);
127       }
128     }
129
130     // Fields which exist only on the right entity.
131     foreach ($right_values as $right_key => $values) {
132       list (, $field_key) = explode(':', $right_key);
133       $compare_settings = $this->pluginsConfig->get('fields.' . $field_key);
134       $result[$right_key] = [
135         '#name' => (isset($compare_settings['settings']['show_header']) && $compare_settings['settings']['show_header'] == 0) ? '' : $values['label'],
136         '#settings' => $compare_settings,
137         '#data' => [],
138       ];
139       $result[$right_key]['#data'] += $this->combineFields([], $right_values[$right_key]);
140     }
141
142     return $result;
143   }
144
145   /**
146    * Combine two fields into an array with keys '#left' and '#right'.
147    *
148    * @param array $left_values
149    *   Entity field formatted into an array of strings.
150    * @param array $right_values
151    *   Entity field formatted into an array of strings.
152    *
153    * @return array
154    *   Array resulted after combining the left and right values.
155    */
156   protected function combineFields(array $left_values, array $right_values) {
157     $result = array(
158       '#left' => array(),
159       '#right' => array(),
160     );
161     $max = max(array(count($left_values), count($right_values)));
162     for ($delta = 0; $delta < $max; $delta++) {
163       // EXPERIMENTAL: Transform thumbnail from ImageFieldBuilder.
164       // @todo Make thumbnail / rich diff data pluggable.
165       // @see https://www.drupal.org/node/2840566
166       if (isset($left_values[$delta])) {
167         $value = $left_values[$delta];
168         if (isset($value['#thumbnail'])) {
169           $result['#left_thumbnail'][] = $value['#thumbnail'];
170         }
171         else {
172           $result['#left'][] = is_array($value) ? implode("\n", $value) : $value;
173         }
174       }
175       if (isset($right_values[$delta])) {
176         $value = $right_values[$delta];
177         if (isset($value['#thumbnail'])) {
178           $result['#right_thumbnail'][] = $value['#thumbnail'];
179         }
180         else {
181           $result['#right'][] = is_array($value) ? implode("\n", $value) : $value;
182         }
183       }
184     }
185
186     // If a field has multiple values combine them into one single string.
187     $result['#left'] = implode("\n", $result['#left']);
188     $result['#right'] = implode("\n", $result['#right']);
189
190     return $result;
191   }
192
193   /**
194    * Prepare the table rows for #type 'table'.
195    *
196    * @param string $a
197    *   The source string to compare from.
198    * @param string $b
199    *   The target string to compare to.
200    * @param bool $show_header
201    *   Display diff context headers. For example, "Line x".
202    * @param array $line_stats
203    *   Tracks line numbers across multiple calls to DiffFormatter.
204    *
205    * @see \Drupal\Component\Diff\DiffFormatter::format
206    *
207    * @return array
208    *   Array of rows usable with #type => 'table' returned by the core diff
209    *   formatter when format a diff.
210    */
211   public function getRows($a, $b, $show_header = FALSE, array &$line_stats = NULL) {
212     if (!isset($line_stats)) {
213       $line_stats = array(
214         'counter' => array('x' => 0, 'y' => 0),
215         'offset' => array('x' => 0, 'y' => 0),
216       );
217     }
218
219     // Header is the line counter.
220     $this->diffFormatter->show_header = $show_header;
221     $diff = new Diff($a, $b);
222
223     return $this->diffFormatter->format($diff);
224   }
225
226   /**
227    * Splits the strings into lines and counts the resulted number of lines.
228    *
229    * @param array $diff
230    *   Array of strings.
231    */
232   public function processStateLine(array &$diff) {
233     $data = $diff['#data'];
234     if (isset($data['#left']) && $data['#left'] != '') {
235       if (is_string($data['#left'])) {
236         $diff['#data']['#left'] = explode("\n", $data['#left']);
237       }
238       $diff['#data']['#count_left'] = count($diff['#data']['#left']);
239     }
240     else {
241       $diff['#data']['#count_left'] = 0;
242       $diff['#data']['#left'] = [];
243     }
244     if (isset($data['#right']) && $data['#right'] != '') {
245       if (is_string($data['#right'])) {
246         $diff['#data']['#right'] = explode("\n", $data['#right']);
247       }
248       $diff['#data']['#count_right'] = count($diff['#data']['#right']);
249     }
250     else {
251       $diff['#data']['#count_right'] = 0;
252       $diff['#data']['#right'] = [];
253     }
254   }
255
256   /**
257    * Gets the revision description of the revision.
258    *
259    * @param \Drupal\Core\Entity\ContentEntityInterface $revision
260    *   The current revision.
261    * @param \Drupal\Core\Entity\ContentEntityInterface $previous_revision
262    *   (optional) The previous revision. Defaults to NULL.
263    *
264    * @return string
265    *   The revision log message.
266    */
267   public function getRevisionDescription(ContentEntityInterface $revision, ContentEntityInterface $previous_revision = NULL) {
268     $revision_summary = '';
269     // Check if the revision has a revision log message.
270     if ($revision instanceof RevisionLogInterface) {
271       $revision_summary = Xss::filter($revision->getRevisionLogMessage());
272     }
273
274     // @todo Autogenerate summary again.
275     // @see https://www.drupal.org/project/diff/issues/2880936
276
277     // Add workflow/content moderation state information.
278     if ($state = $this->getModerationState($revision)) {
279       $revision_summary .= " ($state)";
280     }
281
282     return $revision_summary;
283   }
284
285   /**
286    * Creates an log message based on the changes of entity fields.
287    *
288    * @param \Drupal\Core\Entity\ContentEntityInterface $revision
289    *   The current revision.
290    *
291    * @return array
292    *   Array of the revision fields with their value and label.
293    */
294   protected function summary(ContentEntityInterface $revision) {
295     $result = [];
296     $entity_type_id = $revision->getEntityTypeId();
297     // Loop through entity fields and transform every FieldItemList object
298     // into an array of strings according to field type specific settings.
299     /** @var \Drupal\Core\Field\FieldItemListInterface $field_items */
300     foreach ($revision as $field_items) {
301       $show_delta = FALSE;
302       // Create a plugin instance for the field definition.
303       $plugin = $this->diffBuilderManager->createInstanceForFieldDefinition($field_items->getFieldDefinition());
304       if ($plugin && $this->diffBuilderManager->showDiff($field_items->getFieldDefinition()->getFieldStorageDefinition())) {
305         // Create the array with the fields of the entity. Recursive if the
306         // field contains entities.
307         if ($plugin instanceof FieldReferenceInterface) {
308           foreach ($plugin->getEntitiesToDiff($field_items) as $entity_key => $reference_entity) {
309             foreach ($this->summary($reference_entity) as $key => $build) {
310               if ($field_items->getFieldDefinition()->getFieldStorageDefinition()->getCardinality() != 1) {
311                 $show_delta = TRUE;
312               }
313               $result[$key] = $build;
314               $delta = $show_delta ? '<sub>' . ($entity_key + 1) . '</sub> ' : ' - ';
315               $result[$key]['label'] = $field_items->getFieldDefinition()->getLabel() . $delta . $result[$key]['label'];
316             };
317           }
318         }
319         else {
320           // Create a unique flat key.
321           $key = $revision->id() . ':' . $entity_type_id . '.' . $field_items->getName();
322
323           $result[$key]['value'] = $field_items->getValue();
324           $result[$key]['label'] = $field_items->getFieldDefinition()->getLabel();
325         }
326       }
327     }
328
329     return $result;
330   }
331
332   /**
333    * Gets the revision's content moderation state, if available.
334    *
335    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
336    *   The entity revision.
337    *
338    * @return string|bool
339    *   Returns the label of the moderation state, if available, otherwise FALSE.
340    */
341   protected function getModerationState(ContentEntityInterface $entity) {
342     if ($this->moderationInformation && $this->moderationInformation->isModeratedEntity($entity)) {
343       if ($state = $entity->moderation_state->value) {
344         $workflow = $this->moderationInformation->getWorkflowForEntity($entity);
345         return $workflow->getTypePlugin()->getState($state)->label();
346       }
347     }
348
349     return FALSE;
350   }
351
352   /**
353    * Sets the content moderation service if available.
354    *
355    * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_information
356    *   The moderation information service.
357    */
358   public function setModerationInformation(ModerationInformationInterface $moderation_information) {
359     $this->moderationInformation = $moderation_information;
360   }
361
362 }