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