Yaffs site version 1.1
[yaffs-website] / web / modules / contrib / entity_reference_revisions / src / Plugin / Field / FieldType / EntityReferenceRevisionsItem.php
1 <?php
2
3 namespace Drupal\entity_reference_revisions\Plugin\Field\FieldType;
4
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\Core\Entity\EntityTypeInterface;
7 use Drupal\Core\Entity\TypedData\EntityDataDefinition;
8 use Drupal\Core\Field\FieldDefinitionInterface;
9 use Drupal\Core\Field\FieldStorageDefinitionInterface;
10 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
11 use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
12 use Drupal\Core\Form\FormStateInterface;
13 use Drupal\Core\TypedData\DataReferenceDefinition;
14 use Drupal\Core\TypedData\DataReferenceTargetDefinition;
15 use Drupal\Core\TypedData\OptionsProviderInterface;
16 use Drupal\entity_reference_revisions\EntityNeedsSaveInterface;
17
18 /**
19  * Defines the 'entity_reference_revisions' entity field type.
20  *
21  * Supported settings (below the definition's 'settings' key) are:
22  * - target_type: The entity type to reference. Required.
23  * - target_bundle: (optional): If set, restricts the entity bundles which may
24  *   may be referenced. May be set to an single bundle, or to an array of
25  *   allowed bundles.
26  *
27  * @FieldType(
28  *   id = "entity_reference_revisions",
29  *   label = @Translation("Entity reference revisions"),
30  *   description = @Translation("An entity field containing an entity reference to a specific revision."),
31  *   category = @Translation("Reference revisions"),
32  *   no_ui = FALSE,
33  *   class = "\Drupal\entity_reference_revisions\Plugin\Field\FieldType\EntityReferenceRevisionsItem",
34  *   list_class = "\Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList",
35  *   default_formatter = "entity_reference_revisions_entity_view",
36  *   default_widget = "entity_reference_revisions_autocomplete"
37  * )
38  */
39 class EntityReferenceRevisionsItem extends EntityReferenceItem implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
40
41   /**
42    * {@inheritdoc}
43    */
44   public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
45
46     $entity_types = \Drupal::entityTypeManager()->getDefinitions();
47     $options = array();
48     foreach ($entity_types as $entity_type) {
49       if ($entity_type->isRevisionable()) {
50         $options[$entity_type->id()] = $entity_type->getLabel();
51       }
52     }
53
54     $element['target_type'] = array(
55       '#type' => 'select',
56       '#title' => $this->t('Type of item to reference'),
57       '#options' => $options,
58       '#default_value' => $this->getSetting('target_type'),
59       '#required' => TRUE,
60       '#disabled' => $has_data,
61       '#size' => 1,
62     );
63
64     return $element;
65   }
66
67   /**
68    * {@inheritdoc}
69    */
70   public static function getPreconfiguredOptions() {
71     $options = array();
72
73     // Add all the commonly referenced entity types as distinct pre-configured
74     // options.
75     $entity_types = \Drupal::entityTypeManager()->getDefinitions();
76     $common_references = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
77       return $entity_type->get('common_reference_revisions_target') && $entity_type->isRevisionable();
78     });
79
80     /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
81     foreach ($common_references as $entity_type) {
82
83       $options[$entity_type->id()] = [
84         'label' => $entity_type->getLabel(),
85         'field_storage_config' => [
86           'settings' => [
87             'target_type' => $entity_type->id(),
88           ]
89         ]
90       ];
91       $default_reference_settings = $entity_type->get('default_reference_revision_settings');
92       if (is_array($default_reference_settings)) {
93         $options[$entity_type->id()] = array_merge($options[$entity_type->id()], $default_reference_settings);
94       }
95     }
96
97     return $options;
98   }
99
100   /**
101    * {@inheritdoc}
102    */
103   public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
104     $settings = $field_definition->getSettings();
105     $target_type_info = \Drupal::entityTypeManager()->getDefinition($settings['target_type']);
106
107     $properties = parent::propertyDefinitions($field_definition);
108
109     if ($target_type_info->getKey('revision')) {
110       $target_revision_id_definition = DataReferenceTargetDefinition::create('integer')
111         ->setLabel(t('@label revision ID', array('@label' => $target_type_info->getLabel())))
112         ->setSetting('unsigned', TRUE);
113
114       $target_revision_id_definition->setRequired(TRUE);
115       $properties['target_revision_id'] = $target_revision_id_definition;
116     }
117
118     $properties['entity'] = DataReferenceDefinition::create('entity_revision')
119       ->setLabel($target_type_info->getLabel())
120       ->setDescription(t('The referenced entity revision'))
121       // The entity object is computed out of the entity ID.
122       ->setComputed(TRUE)
123       ->setReadOnly(FALSE)
124       ->setTargetDefinition(EntityDataDefinition::create($settings['target_type']));
125
126     return $properties;
127   }
128
129   /**
130    * {@inheritdoc}
131    */
132   public static function schema(FieldStorageDefinitionInterface $field_definition) {
133     $target_type = $field_definition->getSetting('target_type');
134     $target_type_info = \Drupal::entityTypeManager()->getDefinition($target_type);
135
136     $schema = parent::schema($field_definition);
137
138     if ($target_type_info->getKey('revision')) {
139       $schema['columns']['target_revision_id'] = array(
140         'description' => 'The revision ID of the target entity.',
141         'type' => 'int',
142         'unsigned' => TRUE,
143       );
144       $schema['indexes']['target_revision_id'] = array('target_revision_id');
145     }
146
147     return $schema;
148   }
149
150   /**
151    * {@inheritdoc}
152    */
153   public function setValue($values, $notify = TRUE) {
154     if (isset($values) && !is_array($values)) {
155       // If either a scalar or an object was passed as the value for the item,
156       // assign it to the 'entity' property since that works for both cases.
157       $this->set('entity', $values, $notify);
158     }
159     else {
160       parent::setValue($values, FALSE);
161       // Support setting the field item with only one property, but make sure
162       // values stay in sync if only property is passed.
163       // NULL is a valid value, so we use array_key_exists().
164       if (is_array($values) && array_key_exists('target_id', $values) && !isset($values['entity'])) {
165         $this->onChange('target_id', FALSE);
166       }
167       elseif (is_array($values) && array_key_exists('target_revision_id', $values) && !isset($values['entity'])) {
168         $this->onChange('target_revision_id', FALSE);
169       }
170       elseif (is_array($values) && !array_key_exists('target_id', $values) && !array_key_exists('target_revision_id', $values) && isset($values['entity'])) {
171         $this->onChange('entity', FALSE);
172       }
173       elseif (is_array($values) && array_key_exists('target_id', $values) && isset($values['entity'])) {
174         // If both properties are passed, verify the passed values match. The
175         // only exception we allow is when we have a new entity: in this case
176         // its actual id and target_id will be different, due to the new entity
177         // marker.
178         $entity_id = $this->get('entity')->getTargetIdentifier();
179         // If the entity has been saved and we're trying to set both the
180         // target_id and the entity values with a non-null target ID, then the
181         // value for target_id should match the ID of the entity value.
182         if (!$this->entity->isNew() && $values['target_id'] !== NULL && ($entity_id !== $values['target_id'])) {
183           throw new \InvalidArgumentException('The target id and entity passed to the entity reference item do not match.');
184         }
185       }
186       // Notify the parent if necessary.
187       if ($notify && $this->getParent()) {
188         $this->getParent()->onChange($this->getName());
189       }
190     }
191
192   }
193
194   /**
195    * {@inheritdoc}
196    */
197   public function getValue() {
198     $values = parent::getValue();
199     if ($this->entity instanceof EntityNeedsSaveInterface && $this->entity->needsSave()) {
200       $values['entity'] = $this->entity;
201     }
202     return $values;
203   }
204
205   /**
206    * {@inheritdoc}
207    */
208   public function onChange($property_name, $notify = TRUE) {
209     // Make sure that the target ID and the target property stay in sync.
210     if ($property_name == 'entity') {
211       $property = $this->get('entity');
212       $target_id = $property->isTargetNew() ? NULL : $property->getTargetIdentifier();
213       $this->writePropertyValue('target_id', $target_id);
214       $this->writePropertyValue('target_revision_id', $property->getValue()->getRevisionId());
215     }
216     elseif ($property_name == 'target_id' && $this->target_id != NULL && $this->target_revision_id) {
217       $this->writePropertyValue('entity', array(
218         'target_id' => $this->target_id,
219         'target_revision_id' => $this->target_revision_id,
220       ));
221     }
222     elseif ($property_name == 'target_revision_id' && $this->target_revision_id && $this->target_id) {
223       $this->writePropertyValue('entity', array(
224         'target_id' => $this->target_id,
225         'target_revision_id' => $this->target_revision_id,
226       ));
227     }
228     if ($notify && isset($this->parent)) {
229       $this->parent->onChange($this->name);
230     }
231   }
232
233   /**
234    * {@inheritdoc}
235    */
236   public function isEmpty() {
237     // Avoid loading the entity by first checking the 'target_id'.
238     if ($this->target_id !== NULL && $this->target_revision_id !== NULL) {
239       return FALSE;
240     }
241     if ($this->entity && $this->entity instanceof EntityInterface) {
242       return FALSE;
243     }
244     return TRUE;
245   }
246
247   /**
248    * {@inheritdoc}
249    */
250   public function preSave() {
251     $has_new = $this->hasNewEntity();
252
253     // If it is a new entity, parent will save it.
254     parent::preSave();
255
256     if (!$has_new) {
257       // Create a new revision if it is a composite entity in a host with a new
258       // revision.
259
260       $host = $this->getEntity();
261       $needs_save = $this->entity instanceof EntityNeedsSaveInterface && $this->entity->needsSave();
262       if (!$host->isNew() && $host->isNewRevision() && $this->entity && $this->entity->getEntityType()->get('entity_revision_parent_id_field')) {
263         $this->entity->setNewRevision();
264         if ($host->isDefaultRevision()) {
265           $this->entity->isDefaultRevision(TRUE);
266         }
267         $needs_save = TRUE;
268       }
269       if ($needs_save) {
270         $this->entity->save();
271       }
272     }
273     if ($this->entity) {
274       $this->target_revision_id = $this->entity->getRevisionId();
275     }
276   }
277
278   /**
279    * {@inheritdoc}
280    */
281   public function postSave($update) {
282     parent::postSave($update);
283
284     $needs_save = FALSE;
285     // If any of entity, parent type or parent id is missing then return.
286     if (!$this->entity || !$this->entity->getEntityType()->get('entity_revision_parent_type_field') || !$this->entity->getEntityType()->get('entity_revision_parent_id_field')) {
287       return;
288     }
289
290     $entity = $this->entity;
291     $parent_entity = $this->getEntity();
292
293     // If the entity has a parent field name get the key.
294     if ($entity->getEntityType()->get('entity_revision_parent_field_name_field')) {
295       $parent_field_name = $entity->getEntityType()->get('entity_revision_parent_field_name_field');
296
297       // If parent field name has changed then set it.
298       if ($entity->get($parent_field_name)->value != $this->getFieldDefinition()->getName()) {
299         $entity->set($parent_field_name, $this->getFieldDefinition()->getName());
300         $needs_save = TRUE;
301       }
302     }
303
304     $parent_type = $entity->getEntityType()->get('entity_revision_parent_type_field');
305     $parent_id = $entity->getEntityType()->get('entity_revision_parent_id_field');
306
307     // If the parent type has changed then set it.
308     if ($entity->get($parent_type)->value != $parent_entity->getEntityTypeId()) {
309       $entity->set($parent_type, $parent_entity->getEntityTypeId());
310       $needs_save = TRUE;
311     }
312     // If the parent id has changed then set it.
313     if ($entity->get($parent_id)->value != $parent_entity->id()) {
314       $entity->set($parent_id, $parent_entity->id());
315       $needs_save = TRUE;
316     }
317
318     if ($needs_save) {
319       // Check if any of the keys has changed, save it, do not create a new
320       // revision.
321       $entity->setNewRevision(FALSE);
322       $entity->save();
323     }
324   }
325
326   /**
327    * {@inheritdoc}
328    */
329   public function deleteRevision() {
330     $child = $this->entity;
331     if ($child->isDefaultRevision()) {
332       // Do not delete if it is the default revision.
333       return;
334     }
335
336     $host = $this->getEntity();
337     $field_name = $this->getFieldDefinition()->getName() . '.target_revision_id';
338     $all_revisions = \Drupal::entityQuery($host->getEntityTypeId())
339       ->condition($field_name, $child->getRevisionId())
340       ->allRevisions()
341       ->execute();
342
343     if (count($all_revisions) > 1) {
344       // Do not delete if there is more than one usage of this revision.
345       return;
346     }
347
348     \Drupal::entityTypeManager()->getStorage($child->getEntityTypeId())->deleteRevision($child->getRevisionId());
349   }
350
351   /**
352    * {@inheritdoc}
353    */
354   public function delete() {
355     parent::delete();
356
357     if ($this->entity && $this->entity->getEntityType()->get('entity_revision_parent_type_field') && $this->entity->getEntityType()->get('entity_revision_parent_id_field')) {
358       // Only delete composite entities if the host field is not translatable.
359       if (!$this->getFieldDefinition()->isTranslatable()) {
360         $this->entity->delete();
361       }
362     }
363   }
364  /**
365  * {@inheritdoc}
366  */
367   public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
368     return FALSE;
369   }
370
371   /**
372    * {@inheritdoc}
373    */
374   public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
375     $selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
376     $entity_manager = \Drupal::entityTypeManager();
377
378     // Bail if there are no referenceable entities.
379     if (!$selection_manager->getSelectionHandler($field_definition)->getReferenceableEntities()) {
380       return;
381     }
382
383     // ERR field values are never cross referenced so we need to generate new
384     // target entities. First, find the target entity type.
385     $target_type_id = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
386     $target_type = $entity_manager->getDefinition($target_type_id);
387     $handler_settings = $field_definition->getSetting('handler_settings');
388
389     // Determine referenceable bundles.
390     $bundle_manager = \Drupal::service('entity_type.bundle.info');
391     if (isset($handler_settings['target_bundles']) && is_array($handler_settings['target_bundles'])) {
392       $bundles = $handler_settings['target_bundles'];
393     }
394     else {
395       $bundles = $bundle_manager->getBundleInfo($target_type_id);
396     }
397     $bundle = array_rand($bundles);
398
399     $label = NULL;
400     if ($label_key = $target_type->getKey('label')) {
401       $random = new Random();
402       // @TODO set the length somehow less arbitrary.
403       $label = $random->word(mt_rand(1, 10));
404     }
405
406     // Create entity stub.
407     $entity = $selection_manager->getSelectionHandler($field_definition)->createNewEntity($target_type_id, $bundle, $label, 0);
408
409     // Populate entity values and save.
410     $instances = $entity_manager
411       ->getStorage('field_config')
412       ->loadByProperties([
413         'entity_type' => $target_type_id,
414         'bundle' => $bundle,
415       ]);
416
417     foreach ($instances as $instance) {
418       $field_storage = $instance->getFieldStorageDefinition();
419       $max = $cardinality = $field_storage->getCardinality();
420       if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
421         // Just an arbitrary number for 'unlimited'
422         $max = rand(1, 5);
423       }
424       $field_name = $field_storage->getName();
425       $entity->{$field_name}->generateSampleItems($max);
426     }
427
428     $entity->save();
429
430     return [
431       'target_id' => $entity->id(),
432       'target_revision_id' => $entity->getRevisionId(),
433     ];
434   }
435
436 }