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