Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / field / src / Entity / FieldConfig.php
1 <?php
2
3 namespace Drupal\field\Entity;
4
5 use Drupal\Core\Entity\EntityStorageInterface;
6 use Drupal\Core\Entity\FieldableEntityStorageInterface;
7 use Drupal\Core\Field\FieldConfigBase;
8 use Drupal\Core\Field\FieldException;
9 use Drupal\field\FieldStorageConfigInterface;
10 use Drupal\field\FieldConfigInterface;
11
12 /**
13  * Defines the Field entity.
14  *
15  * @ConfigEntityType(
16  *   id = "field_config",
17  *   label = @Translation("Field"),
18  *   label_collection = @Translation("Fields"),
19  *   label_singular = @Translation("field"),
20  *   label_plural = @Translation("fields"),
21  *   label_count = @PluralTranslation(
22  *     singular = "@count field",
23  *     plural = "@count fields",
24  *   ),
25  *   handlers = {
26  *     "access" = "Drupal\field\FieldConfigAccessControlHandler",
27  *     "storage" = "Drupal\field\FieldConfigStorage"
28  *   },
29  *   config_prefix = "field",
30  *   entity_keys = {
31  *     "id" = "id",
32  *     "label" = "label"
33  *   },
34  *   config_export = {
35  *     "id",
36  *     "field_name",
37  *     "entity_type",
38  *     "bundle",
39  *     "label",
40  *     "description",
41  *     "required",
42  *     "translatable",
43  *     "default_value",
44  *     "default_value_callback",
45  *     "settings",
46  *     "field_type",
47  *   }
48  * )
49  */
50 class FieldConfig extends FieldConfigBase implements FieldConfigInterface {
51
52   /**
53    * Flag indicating whether the field is deleted.
54    *
55    * The delete() method marks the field as "deleted" and removes the
56    * corresponding entry from the config storage, but keeps its definition in
57    * the state storage while field data is purged by a separate
58    * garbage-collection process.
59    *
60    * Deleted fields stay out of the regular entity lifecycle (notably, their
61    * values are not populated in loaded entities, and are not saved back).
62    *
63    * @var bool
64    */
65   protected $deleted = FALSE;
66
67   /**
68    * The associated FieldStorageConfig entity.
69    *
70    * @var \Drupal\field\Entity\FieldStorageConfig
71    */
72   protected $fieldStorage;
73
74   /**
75    * Constructs a FieldConfig object.
76    *
77    * In most cases, Field entities are created via
78    * FieldConfig::create($values), where $values is the same
79    * parameter as in this constructor.
80    *
81    * @param array $values
82    *   An array of field properties, keyed by property name. The
83    *   storage associated with the field can be specified either with:
84    *   - field_storage: the FieldStorageConfigInterface object,
85    *   or by referring to an existing field storage in the current configuration
86    *   with:
87    *   - field_name: The field name.
88    *   - entity_type: The entity type.
89    *   Additionally, a 'bundle' property is required to indicate the entity
90    *   bundle to which the field is attached to. Other array elements will be
91    *   used to set the corresponding properties on the class; see the class
92    *   property documentation for details.
93    *
94    * @see entity_create()
95    */
96   public function __construct(array $values, $entity_type = 'field_config') {
97     // Allow either an injected FieldStorageConfig object, or a field_name and
98     // entity_type.
99     if (isset($values['field_storage'])) {
100       if (!$values['field_storage'] instanceof FieldStorageConfigInterface) {
101         throw new FieldException('Attempt to create a configurable field for a non-configurable field storage.');
102       }
103       $field_storage = $values['field_storage'];
104       $values['field_name'] = $field_storage->getName();
105       $values['entity_type'] = $field_storage->getTargetEntityTypeId();
106       // The internal property is fieldStorage, not field_storage.
107       unset($values['field_storage']);
108       $values['fieldStorage'] = $field_storage;
109     }
110     else {
111       if (empty($values['field_name'])) {
112         throw new FieldException('Attempt to create a field without a field_name.');
113       }
114       if (empty($values['entity_type'])) {
115         throw new FieldException("Attempt to create a field '{$values['field_name']}' without an entity_type.");
116       }
117     }
118     // 'bundle' is required in either case.
119     if (empty($values['bundle'])) {
120       throw new FieldException("Attempt to create a field '{$values['field_name']}' without a bundle.");
121     }
122
123     parent::__construct($values, $entity_type);
124   }
125
126   /**
127    * {@inheritdoc}
128    */
129   public function postCreate(EntityStorageInterface $storage) {
130     parent::postCreate($storage);
131
132     // Validate that we have a valid storage for this field. This throws an
133     // exception if the storage is invalid.
134     $this->getFieldStorageDefinition();
135
136     // 'Label' defaults to the field name (mostly useful for fields created in
137     // tests).
138     if (empty($this->label)) {
139       $this->label = $this->getName();
140     }
141   }
142
143   /**
144    * Overrides \Drupal\Core\Entity\Entity::preSave().
145    *
146    * @throws \Drupal\Core\Field\FieldException
147    *   If the field definition is invalid.
148    * @throws \Drupal\Core\Entity\EntityStorageException
149    *   In case of failures at the configuration storage level.
150    */
151   public function preSave(EntityStorageInterface $storage) {
152     $entity_manager = \Drupal::entityManager();
153     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
154
155     $storage_definition = $this->getFieldStorageDefinition();
156
157     // Filter out unknown settings and make sure all settings are present, so
158     // that a complete field definition is passed to the various hooks and
159     // written to config.
160     $default_settings = $field_type_manager->getDefaultFieldSettings($storage_definition->getType());
161     $this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
162
163     if ($this->isNew()) {
164       // Notify the entity storage.
165       $entity_manager->onFieldDefinitionCreate($this);
166     }
167     else {
168       // Some updates are always disallowed.
169       if ($this->entity_type != $this->original->entity_type) {
170         throw new FieldException("Cannot change an existing field's entity_type.");
171       }
172       if ($this->bundle != $this->original->bundle) {
173         throw new FieldException("Cannot change an existing field's bundle.");
174       }
175       if ($storage_definition->uuid() != $this->original->getFieldStorageDefinition()->uuid()) {
176         throw new FieldException("Cannot change an existing field's storage.");
177       }
178       // Notify the entity storage.
179       $entity_manager->onFieldDefinitionUpdate($this, $this->original);
180     }
181
182     parent::preSave($storage);
183   }
184
185   /**
186    * {@inheritdoc}
187    */
188   public function calculateDependencies() {
189     parent::calculateDependencies();
190     // Mark the field_storage_config as a dependency.
191     $this->addDependency('config', $this->getFieldStorageDefinition()->getConfigDependencyName());
192     return $this;
193   }
194
195   /**
196    * {@inheritdoc}
197    */
198   public static function preDelete(EntityStorageInterface $storage, array $fields) {
199     /** @var \Drupal\Core\Field\DeletedFieldsRepositoryInterface $deleted_fields_repository */
200     $deleted_fields_repository = \Drupal::service('entity_field.deleted_fields_repository');
201     $entity_type_manager = \Drupal::entityTypeManager();
202
203     parent::preDelete($storage, $fields);
204
205     // Keep the field definitions in the deleted fields repository so we can use
206     // them later during field_purge_batch().
207     /** @var \Drupal\field\FieldConfigInterface $field */
208     foreach ($fields as $field) {
209       // Only mark a field for purging if there is data. Otherwise, just remove
210       // it.
211       $target_entity_storage = $entity_type_manager->getStorage($field->getTargetEntityTypeId());
212       if (!$field->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field->getFieldStorageDefinition(), TRUE)) {
213         $field = clone $field;
214         $field->deleted = TRUE;
215         $field->fieldStorage = NULL;
216         $deleted_fields_repository->addFieldDefinition($field);
217       }
218     }
219   }
220
221   /**
222    * {@inheritdoc}
223    */
224   public static function postDelete(EntityStorageInterface $storage, array $fields) {
225     // Clear the cache upfront, to refresh the results of getBundles().
226     \Drupal::entityManager()->clearCachedFieldDefinitions();
227
228     // Notify the entity storage.
229     foreach ($fields as $field) {
230       if (!$field->deleted) {
231         \Drupal::entityManager()->onFieldDefinitionDelete($field);
232       }
233     }
234
235     // If this is part of a configuration synchronization then the following
236     // configuration updates are not necessary.
237     $entity = reset($fields);
238     if ($entity->isSyncing()) {
239       return;
240     }
241
242     // Delete the associated field storages if they are not used anymore and are
243     // not persistent.
244     $storages_to_delete = [];
245     foreach ($fields as $field) {
246       $storage_definition = $field->getFieldStorageDefinition();
247       if (!$field->deleted && !$field->isUninstalling() && $storage_definition->isDeletable()) {
248         // Key by field UUID to avoid deleting the same storage twice.
249         $storages_to_delete[$storage_definition->uuid()] = $storage_definition;
250       }
251     }
252     if ($storages_to_delete) {
253       \Drupal::entityManager()->getStorage('field_storage_config')->delete($storages_to_delete);
254     }
255   }
256
257   /**
258    * {@inheritdoc}
259    */
260   protected function linkTemplates() {
261     $link_templates = parent::linkTemplates();
262     if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
263       $link_templates["{$this->entity_type}-field-edit-form"] = 'entity.field_config.' . $this->entity_type . '_field_edit_form';
264       $link_templates["{$this->entity_type}-storage-edit-form"] = 'entity.field_config.' . $this->entity_type . '_storage_edit_form';
265       $link_templates["{$this->entity_type}-field-delete-form"] = 'entity.field_config.' . $this->entity_type . '_field_delete_form';
266
267       if (isset($link_templates['config-translation-overview'])) {
268         $link_templates["config-translation-overview.{$this->entity_type}"] = "entity.field_config.config_translation_overview.{$this->entity_type}";
269       }
270     }
271     return $link_templates;
272   }
273
274   /**
275    * {@inheritdoc}
276    */
277   protected function urlRouteParameters($rel) {
278     $parameters = parent::urlRouteParameters($rel);
279     $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
280     $bundle_parameter_key = $entity_type->getBundleEntityType() ?: 'bundle';
281     $parameters[$bundle_parameter_key] = $this->bundle;
282     return $parameters;
283   }
284
285   /**
286    * {@inheritdoc}
287    */
288   public function isDeleted() {
289     return $this->deleted;
290   }
291
292   /**
293    * {@inheritdoc}
294    */
295   public function getFieldStorageDefinition() {
296     if (!$this->fieldStorage) {
297       $field_storage_definition = NULL;
298
299       $field_storage_definitions = $this->entityManager()->getFieldStorageDefinitions($this->entity_type);
300       if (isset($field_storage_definitions[$this->field_name])) {
301         $field_storage_definition = $field_storage_definitions[$this->field_name];
302       }
303       // If this field has been deleted, try to find its field storage
304       // definition in the deleted fields repository.
305       elseif ($this->deleted) {
306         $deleted_storage_definitions = \Drupal::service('entity_field.deleted_fields_repository')->getFieldStorageDefinitions();
307         foreach ($deleted_storage_definitions as $deleted_storage_definition) {
308           if ($deleted_storage_definition->getName() === $this->field_name) {
309             $field_storage_definition = $deleted_storage_definition;
310           }
311         }
312       }
313
314       if (!$field_storage_definition) {
315         throw new FieldException("Attempt to create a field {$this->field_name} that does not exist on entity type {$this->entity_type}.");
316       }
317       if (!$field_storage_definition instanceof FieldStorageConfigInterface) {
318         throw new FieldException("Attempt to create a configurable field of non-configurable field storage {$this->field_name}.");
319       }
320       $this->fieldStorage = $field_storage_definition;
321     }
322
323     return $this->fieldStorage;
324   }
325
326   /**
327    * {@inheritdoc}
328    */
329   public function isDisplayConfigurable($context) {
330     return TRUE;
331   }
332
333   /**
334    * {@inheritdoc}
335    */
336   public function getDisplayOptions($display_context) {
337     // Hide configurable fields by default.
338     return ['region' => 'hidden'];
339   }
340
341   /**
342    * {@inheritdoc}
343    */
344   public function isReadOnly() {
345     return FALSE;
346   }
347
348   /**
349    * {@inheritdoc}
350    */
351   public function isComputed() {
352     return FALSE;
353   }
354
355   /**
356    * {@inheritdoc}
357    */
358   public function getUniqueIdentifier() {
359     return $this->uuid();
360   }
361
362   /**
363    * Loads a field config entity based on the entity type and field name.
364    *
365    * @param string $entity_type_id
366    *   ID of the entity type.
367    * @param string $bundle
368    *   Bundle name.
369    * @param string $field_name
370    *   Name of the field.
371    *
372    * @return static
373    *   The field config entity if one exists for the provided field
374    *   name, otherwise NULL.
375    */
376   public static function loadByName($entity_type_id, $bundle, $field_name) {
377     return \Drupal::entityManager()->getStorage('field_config')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
378   }
379
380 }