Security update for Core, with self-updated composer
[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     $state = \Drupal::state();
193     $entity_type_manager = \Drupal::entityTypeManager();
194
195     parent::preDelete($storage, $fields);
196     // Keep the field definitions in the state storage so we can use them
197     // later during field_purge_batch().
198     $deleted_fields = $state->get('field.field.deleted') ?: [];
199
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         $config = $field->toArray();
207         $config['deleted'] = TRUE;
208         $config['field_storage_uuid'] = $field->getFieldStorageDefinition()->uuid();
209         $deleted_fields[$field->uuid()] = $config;
210       }
211     }
212     $state->set('field.field.deleted', $deleted_fields);
213   }
214
215   /**
216    * {@inheritdoc}
217    */
218   public static function postDelete(EntityStorageInterface $storage, array $fields) {
219     // Clear the cache upfront, to refresh the results of getBundles().
220     \Drupal::entityManager()->clearCachedFieldDefinitions();
221
222     // Notify the entity storage.
223     foreach ($fields as $field) {
224       if (!$field->deleted) {
225         \Drupal::entityManager()->onFieldDefinitionDelete($field);
226       }
227     }
228
229     // If this is part of a configuration synchronization then the following
230     // configuration updates are not necessary.
231     $entity = reset($fields);
232     if ($entity->isSyncing()) {
233       return;
234     }
235
236     // Delete the associated field storages if they are not used anymore and are
237     // not persistent.
238     $storages_to_delete = [];
239     foreach ($fields as $field) {
240       $storage_definition = $field->getFieldStorageDefinition();
241       if (!$field->deleted && !$field->isUninstalling() && $storage_definition->isDeletable()) {
242         // Key by field UUID to avoid deleting the same storage twice.
243         $storages_to_delete[$storage_definition->uuid()] = $storage_definition;
244       }
245     }
246     if ($storages_to_delete) {
247       \Drupal::entityManager()->getStorage('field_storage_config')->delete($storages_to_delete);
248     }
249   }
250
251   /**
252    * {@inheritdoc}
253    */
254   protected function linkTemplates() {
255     $link_templates = parent::linkTemplates();
256     if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
257       $link_templates["{$this->entity_type}-field-edit-form"] = 'entity.field_config.' . $this->entity_type . '_field_edit_form';
258       $link_templates["{$this->entity_type}-storage-edit-form"] = 'entity.field_config.' . $this->entity_type . '_storage_edit_form';
259       $link_templates["{$this->entity_type}-field-delete-form"] = 'entity.field_config.' . $this->entity_type . '_field_delete_form';
260
261       if (isset($link_templates['config-translation-overview'])) {
262         $link_templates["config-translation-overview.{$this->entity_type}"] = "entity.field_config.config_translation_overview.{$this->entity_type}";
263       }
264     }
265     return $link_templates;
266   }
267
268   /**
269    * {@inheritdoc}
270    */
271   protected function urlRouteParameters($rel) {
272     $parameters = parent::urlRouteParameters($rel);
273     $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
274     $bundle_parameter_key = $entity_type->getBundleEntityType() ?: 'bundle';
275     $parameters[$bundle_parameter_key] = $this->bundle;
276     return $parameters;
277   }
278
279   /**
280    * {@inheritdoc}
281    */
282   public function isDeleted() {
283     return $this->deleted;
284   }
285
286   /**
287    * {@inheritdoc}
288    */
289   public function getFieldStorageDefinition() {
290     if (!$this->fieldStorage) {
291       $fields = $this->entityManager()->getFieldStorageDefinitions($this->entity_type);
292       if (!isset($fields[$this->field_name])) {
293         throw new FieldException("Attempt to create a field {$this->field_name} that does not exist on entity type {$this->entity_type}.");
294       }
295       if (!$fields[$this->field_name] instanceof FieldStorageConfigInterface) {
296         throw new FieldException("Attempt to create a configurable field of non-configurable field storage {$this->field_name}.");
297       }
298       $this->fieldStorage = $fields[$this->field_name];
299     }
300
301     return $this->fieldStorage;
302   }
303
304   /**
305    * {@inheritdoc}
306    */
307   public function isDisplayConfigurable($context) {
308     return TRUE;
309   }
310
311   /**
312    * {@inheritdoc}
313    */
314   public function getDisplayOptions($display_context) {
315     // Hide configurable fields by default.
316     return ['region' => 'hidden'];
317   }
318
319   /**
320    * {@inheritdoc}
321    */
322   public function isReadOnly() {
323     return FALSE;
324   }
325
326   /**
327    * {@inheritdoc}
328    */
329   public function isComputed() {
330     return FALSE;
331   }
332
333   /**
334    * Loads a field config entity based on the entity type and field name.
335    *
336    * @param string $entity_type_id
337    *   ID of the entity type.
338    * @param string $bundle
339    *   Bundle name.
340    * @param string $field_name
341    *   Name of the field.
342    *
343    * @return static
344    *   The field config entity if one exists for the provided field
345    *   name, otherwise NULL.
346    */
347   public static function loadByName($entity_type_id, $bundle, $field_name) {
348     return \Drupal::entityManager()->getStorage('field_config')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
349   }
350
351 }