Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Config / Entity / ConfigEntityStorage.php
1 <?php
2
3 namespace Drupal\Core\Config\Entity;
4
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\Config\ConfigImporterException;
8 use Drupal\Core\Entity\EntityInterface;
9 use Drupal\Core\Entity\EntityMalformedException;
10 use Drupal\Core\Entity\EntityStorageBase;
11 use Drupal\Core\Config\Config;
12 use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
13 use Drupal\Core\Entity\EntityTypeInterface;
14 use Drupal\Component\Uuid\UuidInterface;
15 use Drupal\Core\Language\LanguageManagerInterface;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
17
18 /**
19  * Defines the storage class for configuration entities.
20  *
21  * Configuration object names of configuration entities are comprised of two
22  * parts, separated by a dot:
23  * - config_prefix: A string denoting the owner (module/extension) of the
24  *   configuration object, followed by arbitrary other namespace identifiers
25  *   that are declared by the owning extension; e.g., 'node.type'. The
26  *   config_prefix does NOT contain a trailing dot. It is defined by the entity
27  *   type's annotation.
28  * - ID: A string denoting the entity ID within the entity type namespace; e.g.,
29  *   'article'. Entity IDs may contain dots/periods. The entire remaining string
30  *   after the config_prefix in a config name forms the entity ID. Additional or
31  *   custom suffixes are not possible.
32  *
33  * @ingroup entity_api
34  */
35 class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStorageInterface, ImportableEntityStorageInterface {
36
37   /**
38    * Length limit of the configuration entity ID.
39    *
40    * Most file systems limit a file name's length to 255 characters, so
41    * ConfigBase::MAX_NAME_LENGTH restricts the full configuration object name
42    * to 250 characters (leaving 5 for the file extension). The config prefix
43    * is limited by ConfigEntityType::PREFIX_LENGTH to 83 characters, so this
44    * leaves 166 remaining characters for the configuration entity ID, with 1
45    * additional character needed for the joining dot.
46    *
47    * @see \Drupal\Core\Config\ConfigBase::MAX_NAME_LENGTH
48    * @see \Drupal\Core\Config\Entity\ConfigEntityType::PREFIX_LENGTH
49    */
50   const MAX_ID_LENGTH = 166;
51
52   /**
53    * {@inheritdoc}
54    */
55   protected $uuidKey = 'uuid';
56
57   /**
58    * The config factory service.
59    *
60    * @var \Drupal\Core\Config\ConfigFactoryInterface
61    */
62   protected $configFactory;
63
64   /**
65    * The config storage service.
66    *
67    * @var \Drupal\Core\Config\StorageInterface
68    */
69   protected $configStorage;
70
71   /**
72    * The language manager.
73    *
74    * @var \Drupal\Core\Language\LanguageManagerInterface
75    */
76   protected $languageManager;
77
78   /**
79    * Static cache of entities, keyed first by entity ID, then by an extra key.
80    *
81    * The additional cache key is to maintain separate caches for different
82    * states of config overrides.
83    *
84    * @var array
85    * @see \Drupal\Core\Config\ConfigFactoryInterface::getCacheKeys().
86    */
87   protected $entities = [];
88
89   /**
90    * Determines if the underlying configuration is retrieved override free.
91    *
92    * @var bool
93    */
94   protected $overrideFree = FALSE;
95
96   /**
97    * Constructs a ConfigEntityStorage object.
98    *
99    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
100    *   The entity type definition.
101    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
102    *   The config factory service.
103    * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
104    *   The UUID service.
105    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
106    *   The language manager.
107    */
108   public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
109     parent::__construct($entity_type);
110
111     $this->configFactory = $config_factory;
112     $this->uuidService = $uuid_service;
113     $this->languageManager = $language_manager;
114   }
115
116   /**
117    * {@inheritdoc}
118    */
119   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
120     return new static(
121       $entity_type,
122       $container->get('config.factory'),
123       $container->get('uuid'),
124       $container->get('language_manager')
125     );
126   }
127
128   /**
129    * {@inheritdoc}
130    */
131   public function loadRevision($revision_id) {
132     return NULL;
133   }
134
135   /**
136    * {@inheritdoc}
137    */
138   public function deleteRevision($revision_id) {
139     return NULL;
140   }
141
142   /**
143    * Returns the prefix used to create the configuration name.
144    *
145    * The prefix consists of the config prefix from the entity type plus a dot
146    * for separating from the ID.
147    *
148    * @return string
149    *   The full configuration prefix, for example 'views.view.'.
150    */
151   protected function getPrefix() {
152     return $this->entityType->getConfigPrefix() . '.';
153   }
154
155   /**
156    * {@inheritdoc}
157    */
158   public static function getIDFromConfigName($config_name, $config_prefix) {
159     return substr($config_name, strlen($config_prefix . '.'));
160   }
161
162   /**
163    * {@inheritdoc}
164    */
165   protected function doLoadMultiple(array $ids = NULL) {
166     $prefix = $this->getPrefix();
167
168     // Get the names of the configuration entities we are going to load.
169     if ($ids === NULL) {
170       $names = $this->configFactory->listAll($prefix);
171     }
172     else {
173       $names = [];
174       foreach ($ids as $id) {
175         // Add the prefix to the ID to serve as the configuration object name.
176         $names[] = $prefix . $id;
177       }
178     }
179
180     // Load all of the configuration entities.
181     /** @var \Drupal\Core\Config\Config[] $configs */
182     $configs = [];
183     $records = [];
184     foreach ($this->configFactory->loadMultiple($names) as $config) {
185       $id = $config->get($this->idKey);
186       $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
187       $configs[$id] = $config;
188     }
189     $entities = $this->mapFromStorageRecords($records, $configs);
190
191     // Config entities wrap config objects, and therefore they need to inherit
192     // the cacheability metadata of config objects (to ensure e.g. additional
193     // cacheability metadata added by config overrides is not lost).
194     foreach ($entities as $id => $entity) {
195       // But rather than simply inheriting all cacheability metadata of config
196       // objects, we need to make sure the self-referring cache tag that is
197       // present on Config objects is not added to the Config entity. It must be
198       // removed for 3 reasons:
199       // 1. When renaming/duplicating a Config entity, the cache tag of the
200       //    original config object would remain present, which would be wrong.
201       // 2. Some Config entities choose to not use the cache tag that the under-
202       //    lying Config object provides by default (For performance and
203       //    cacheability reasons it may not make sense to have a unique cache
204       //    tag for every Config entity. The DateFormat Config entity specifies
205       //    the 'rendered' cache tag for example, because A) date formats are
206       //    changed extremely rarely, so invalidating all render cache items is
207       //    fine, B) it means fewer cache tags per page.).
208       // 3. Fewer cache tags is better for performance.
209       $self_referring_cache_tag = ['config:' . $configs[$id]->getName()];
210       $config_cacheability = CacheableMetadata::createFromObject($configs[$id]);
211       $config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag));
212       $entity->addCacheableDependency($config_cacheability);
213     }
214
215     return $entities;
216   }
217
218   /**
219    * {@inheritdoc}
220    */
221   protected function doCreate(array $values) {
222     // Set default language to current language if not provided.
223     $values += [$this->langcodeKey => $this->languageManager->getCurrentLanguage()->getId()];
224     $entity = new $this->entityClass($values, $this->entityTypeId);
225
226     return $entity;
227   }
228
229   /**
230    * {@inheritdoc}
231    */
232   protected function doDelete($entities) {
233     foreach ($entities as $entity) {
234       $this->configFactory->getEditable($this->getPrefix() . $entity->id())->delete();
235     }
236   }
237
238   /**
239    * Implements Drupal\Core\Entity\EntityStorageInterface::save().
240    *
241    * @throws EntityMalformedException
242    *   When attempting to save a configuration entity that has no ID.
243    */
244   public function save(EntityInterface $entity) {
245     // Configuration entity IDs are strings, and '0' is a valid ID.
246     $id = $entity->id();
247     if ($id === NULL || $id === '') {
248       throw new EntityMalformedException('The entity does not have an ID.');
249     }
250
251     // Check the configuration entity ID length.
252     // @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
253     // @todo Consider moving this to a protected method on the parent class, and
254     //   abstracting it for all entity types.
255     if (strlen($entity->get($this->idKey)) > self::MAX_ID_LENGTH) {
256       throw new ConfigEntityIdLengthException("Configuration entity ID {$entity->get($this->idKey)} exceeds maximum allowed length of " . self::MAX_ID_LENGTH . " characters.");
257     }
258
259     return parent::save($entity);
260   }
261
262   /**
263    * {@inheritdoc}
264    */
265   protected function doSave($id, EntityInterface $entity) {
266     $is_new = $entity->isNew();
267     $prefix = $this->getPrefix();
268     $config_name = $prefix . $entity->id();
269     if ($id !== $entity->id()) {
270       // Renaming a config object needs to cater for:
271       // - Storage needs to access the original object.
272       // - The object needs to be renamed/copied in ConfigFactory and reloaded.
273       // - All instances of the object need to be renamed.
274       $this->configFactory->rename($prefix . $id, $config_name);
275     }
276     $config = $this->configFactory->getEditable($config_name);
277
278     // Retrieve the desired properties and set them in config.
279     $config->setData($this->mapToStorageRecord($entity));
280     $config->save($entity->hasTrustedData());
281
282     // Update the entity with the values stored in configuration. It is possible
283     // that configuration schema has casted some of the values.
284     if (!$entity->hasTrustedData()) {
285       $data = $this->mapFromStorageRecords([$config->get()]);
286       $updated_entity = current($data);
287
288       foreach (array_keys($config->get()) as $property) {
289         $value = $updated_entity->get($property);
290         $entity->set($property, $value);
291       }
292     }
293
294     return $is_new ? SAVED_NEW : SAVED_UPDATED;
295   }
296
297   /**
298    * Maps from an entity object to the storage record.
299    *
300    * @param \Drupal\Core\Entity\EntityInterface $entity
301    *   The entity object.
302    *
303    * @return array
304    *   The record to store.
305    */
306   protected function mapToStorageRecord(EntityInterface $entity) {
307     return $entity->toArray();
308   }
309
310   /**
311    * {@inheritdoc}
312    */
313   protected function has($id, EntityInterface $entity) {
314     $prefix = $this->getPrefix();
315     $config = $this->configFactory->get($prefix . $id);
316     return !$config->isNew();
317   }
318
319   /**
320    * {@inheritdoc}
321    */
322   public function hasData() {
323     return (bool) $this->configFactory->listAll($this->getPrefix());
324   }
325
326   /**
327    * Gets entities from the static cache.
328    *
329    * @param array $ids
330    *   If not empty, return entities that match these IDs.
331    *
332    * @return \Drupal\Core\Entity\EntityInterface[]
333    *   Array of entities from the entity cache.
334    */
335   protected function getFromStaticCache(array $ids) {
336     $entities = [];
337     // Load any available entities from the internal cache.
338     if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
339       $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
340       foreach ($ids as $id) {
341         if (!empty($this->entities[$id])) {
342           if (isset($this->entities[$id][$config_overrides_key])) {
343             $entities[$id] = $this->entities[$id][$config_overrides_key];
344           }
345         }
346       }
347     }
348     return $entities;
349   }
350
351   /**
352    * Stores entities in the static entity cache.
353    *
354    * @param \Drupal\Core\Entity\EntityInterface[] $entities
355    *   Entities to store in the cache.
356    */
357   protected function setStaticCache(array $entities) {
358     if ($this->entityType->isStaticallyCacheable()) {
359       $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
360       foreach ($entities as $id => $entity) {
361         $this->entities[$id][$config_overrides_key] = $entity;
362       }
363     }
364   }
365
366   /**
367    * Invokes a hook on behalf of the entity.
368    *
369    * @param $hook
370    *   One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
371    * @param $entity
372    *   The entity object.
373    */
374   protected function invokeHook($hook, EntityInterface $entity) {
375     // Invoke the hook.
376     $this->moduleHandler->invokeAll($this->entityTypeId . '_' . $hook, [$entity]);
377     // Invoke the respective entity-level hook.
378     $this->moduleHandler->invokeAll('entity_' . $hook, [$entity, $this->entityTypeId]);
379   }
380
381   /**
382    * {@inheritdoc}
383    */
384   protected function getQueryServiceName() {
385     return 'entity.query.config';
386   }
387
388   /**
389    * {@inheritdoc}
390    */
391   public function importCreate($name, Config $new_config, Config $old_config) {
392     $entity = $this->_doCreateFromStorageRecord($new_config->get(), TRUE);
393     $entity->save();
394     return TRUE;
395   }
396
397   /**
398    * {@inheritdoc}
399    */
400   public function importUpdate($name, Config $new_config, Config $old_config) {
401     $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
402     $entity = $this->load($id);
403     if (!$entity) {
404       throw new ConfigImporterException("Attempt to update non-existing entity '$id'.");
405     }
406     $entity->setSyncing(TRUE);
407     $entity = $this->updateFromStorageRecord($entity, $new_config->get());
408     $entity->save();
409     return TRUE;
410   }
411
412   /**
413    * {@inheritdoc}
414    */
415   public function importDelete($name, Config $new_config, Config $old_config) {
416     $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
417     $entity = $this->load($id);
418     $entity->setSyncing(TRUE);
419     $entity->delete();
420     return TRUE;
421   }
422
423   /**
424    * {@inheritdoc}
425    */
426   public function importRename($old_name, Config $new_config, Config $old_config) {
427     return $this->importUpdate($old_name, $new_config, $old_config);
428   }
429
430   /**
431    * {@inheritdoc}
432    */
433   public function createFromStorageRecord(array $values) {
434     return $this->_doCreateFromStorageRecord($values);
435   }
436
437   /**
438    * Helps create a configuration entity from storage values.
439    *
440    * Allows the configuration entity storage to massage storage values before
441    * creating an entity.
442    *
443    * @param array $values
444    *   The array of values from the configuration storage.
445    * @param bool $is_syncing
446    *   Is the configuration entity being created as part of a config sync.
447    *
448    * @return \Drupal\Core\Config\ConfigEntityInterface
449    *   The configuration entity.
450    *
451    * @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::createFromStorageRecord()
452    * @see \Drupal\Core\Config\Entity\ImportableEntityStorageInterface::importCreate()
453    */
454   protected function _doCreateFromStorageRecord(array $values, $is_syncing = FALSE) {
455     // Assign a new UUID if there is none yet.
456     if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
457       $values[$this->uuidKey] = $this->uuidService->generate();
458     }
459     $data = $this->mapFromStorageRecords([$values]);
460     $entity = current($data);
461     $entity->original = clone $entity;
462     $entity->setSyncing($is_syncing);
463     $entity->enforceIsNew();
464     $entity->postCreate($this);
465
466     // Modules might need to add or change the data initially held by the new
467     // entity object, for instance to fill-in default values.
468     $this->invokeHook('create', $entity);
469     return $entity;
470
471   }
472
473   /**
474    * {@inheritdoc}
475    */
476   public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
477     $entity->original = clone $entity;
478
479     $data = $this->mapFromStorageRecords([$values]);
480     $updated_entity = current($data);
481
482     foreach (array_keys($values) as $property) {
483       $value = $updated_entity->get($property);
484       $entity->set($property, $value);
485     }
486
487     return $entity;
488   }
489
490   /**
491    * {@inheritdoc}
492    */
493   public function loadOverrideFree($id) {
494     $entities = $this->loadMultipleOverrideFree([$id]);
495     return isset($entities[$id]) ? $entities[$id] : NULL;
496   }
497
498   /**
499    * {@inheritdoc}
500    */
501   public function loadMultipleOverrideFree(array $ids = NULL) {
502     $this->overrideFree = TRUE;
503     $entities = $this->loadMultiple($ids);
504     $this->overrideFree = FALSE;
505     return $entities;
506   }
507
508 }