Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Config / Entity / ConfigEntityBase.php
1 <?php
2
3 namespace Drupal\Core\Config\Entity;
4
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Config\Schema\SchemaIncompleteException;
8 use Drupal\Core\Entity\Entity;
9 use Drupal\Core\Config\ConfigDuplicateUUIDException;
10 use Drupal\Core\Entity\EntityStorageInterface;
11 use Drupal\Core\Entity\EntityTypeInterface;
12 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
13 use Drupal\Core\Plugin\PluginDependencyTrait;
14
15 /**
16  * Defines a base configuration entity class.
17  *
18  * @ingroup entity_api
19  */
20 abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface {
21
22   use PluginDependencyTrait {
23     addDependency as addDependencyTrait;
24   }
25
26   /**
27    * The original ID of the configuration entity.
28    *
29    * The ID of a configuration entity is a unique string (machine name). When a
30    * configuration entity is updated and its machine name is renamed, the
31    * original ID needs to be known.
32    *
33    * @var string
34    */
35   protected $originalId;
36
37   /**
38    * The enabled/disabled status of the configuration entity.
39    *
40    * @var bool
41    */
42   protected $status = TRUE;
43
44   /**
45    * The UUID for this entity.
46    *
47    * @var string
48    */
49   protected $uuid;
50
51   /**
52    * Whether the config is being created, updated or deleted through the
53    * import process.
54    *
55    * @var bool
56    */
57   private $isSyncing = FALSE;
58
59   /**
60    * Whether the config is being deleted by the uninstall process.
61    *
62    * @var bool
63    */
64   private $isUninstalling = FALSE;
65
66   /**
67    * The language code of the entity's default language.
68    *
69    * Assumed to be English by default. ConfigEntityStorage will set an
70    * appropriate language when creating new entities. This default applies to
71    * imported default configuration where the language code is missing. Those
72    * should be assumed to be English. All configuration entities support third
73    * party settings, so even configuration entities that do not directly
74    * store settings involving text in a human language may have such third
75    * party settings attached. This means configuration entities should be in one
76    * of the configured languages or the built-in English.
77    *
78    * @var string
79    */
80   protected $langcode = 'en';
81
82   /**
83    * Third party entity settings.
84    *
85    * An array of key/value pairs keyed by provider.
86    *
87    * @var array
88    */
89   protected $third_party_settings = [];
90
91   /**
92    * Information maintained by Drupal core about configuration.
93    *
94    * Keys:
95    * - default_config_hash: A hash calculated by the config.installer service
96    *   and added during installation.
97    *
98    * @var array
99    */
100   protected $_core = [];
101
102   /**
103    * Trust supplied data and not use configuration schema on save.
104    *
105    * @var bool
106    */
107   protected $trustedData = FALSE;
108
109   /**
110    * {@inheritdoc}
111    */
112   public function __construct(array $values, $entity_type) {
113     parent::__construct($values, $entity_type);
114
115     // Backup the original ID, if any.
116     // Configuration entity IDs are strings, and '0' is a valid ID.
117     $original_id = $this->id();
118     if ($original_id !== NULL && $original_id !== '') {
119       $this->setOriginalId($original_id);
120     }
121   }
122
123   /**
124    * {@inheritdoc}
125    */
126   public function getOriginalId() {
127     return $this->originalId;
128   }
129
130   /**
131    * {@inheritdoc}
132    */
133   public function setOriginalId($id) {
134     // Do not call the parent method since that would mark this entity as no
135     // longer new. Unlike content entities, new configuration entities have an
136     // ID.
137     // @todo https://www.drupal.org/node/2478811 Document the entity life cycle
138     //   and the differences between config and content.
139     $this->originalId = $id;
140     return $this;
141   }
142
143   /**
144    * Overrides Entity::isNew().
145    *
146    * EntityInterface::enforceIsNew() is only supported for newly created
147    * configuration entities but has no effect after saving, since each
148    * configuration entity is unique.
149    */
150   public function isNew() {
151     return !empty($this->enforceIsNew);
152   }
153
154   /**
155    * {@inheritdoc}
156    */
157   public function get($property_name) {
158     return isset($this->{$property_name}) ? $this->{$property_name} : NULL;
159   }
160
161   /**
162    * {@inheritdoc}
163    */
164   public function set($property_name, $value) {
165     if ($this instanceof EntityWithPluginCollectionInterface) {
166       $plugin_collections = $this->getPluginCollections();
167       if (isset($plugin_collections[$property_name])) {
168         // If external code updates the settings, pass it along to the plugin.
169         $plugin_collections[$property_name]->setConfiguration($value);
170       }
171     }
172
173     $this->{$property_name} = $value;
174
175     return $this;
176   }
177
178   /**
179    * {@inheritdoc}
180    */
181   public function enable() {
182     return $this->setStatus(TRUE);
183   }
184
185   /**
186    * {@inheritdoc}
187    */
188   public function disable() {
189     return $this->setStatus(FALSE);
190   }
191
192   /**
193    * {@inheritdoc}
194    */
195   public function setStatus($status) {
196     $this->status = (bool) $status;
197     return $this;
198   }
199
200   /**
201    * {@inheritdoc}
202    */
203   public function status() {
204     return !empty($this->status);
205   }
206
207   /**
208    * {@inheritdoc}
209    */
210   public function setSyncing($syncing) {
211     $this->isSyncing = $syncing;
212
213     return $this;
214   }
215
216   /**
217    * {@inheritdoc}
218    */
219   public function isSyncing() {
220     return $this->isSyncing;
221   }
222
223   /**
224    * {@inheritdoc}
225    */
226   public function setUninstalling($uninstalling) {
227     $this->isUninstalling = $uninstalling;
228   }
229
230   /**
231    * {@inheritdoc}
232    */
233   public function isUninstalling() {
234     return $this->isUninstalling;
235   }
236
237   /**
238    * {@inheritdoc}
239    */
240   public function createDuplicate() {
241     $duplicate = parent::createDuplicate();
242
243     // Prevent the new duplicate from being misinterpreted as a rename.
244     $duplicate->setOriginalId(NULL);
245     return $duplicate;
246   }
247
248   /**
249    * Helper callback for uasort() to sort configuration entities by weight and label.
250    */
251   public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
252     $a_weight = isset($a->weight) ? $a->weight : 0;
253     $b_weight = isset($b->weight) ? $b->weight : 0;
254     if ($a_weight == $b_weight) {
255       $a_label = $a->label();
256       $b_label = $b->label();
257       return strnatcasecmp($a_label, $b_label);
258     }
259     return ($a_weight < $b_weight) ? -1 : 1;
260   }
261
262   /**
263    * {@inheritdoc}
264    */
265   public function toArray() {
266     $properties = [];
267     /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
268     $entity_type = $this->getEntityType();
269
270     $properties_to_export = $entity_type->getPropertiesToExport();
271     if (empty($properties_to_export)) {
272       $config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
273       $definition = $this->getTypedConfig()->getDefinition($config_name);
274       if (!isset($definition['mapping'])) {
275         throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
276       }
277       $properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
278     }
279
280     $id_key = $entity_type->getKey('id');
281     foreach ($properties_to_export as $property_name => $export_name) {
282       // Special handling for IDs so that computed compound IDs work.
283       // @see \Drupal\Core\Entity\EntityDisplayBase::id()
284       if ($property_name == $id_key) {
285         $properties[$export_name] = $this->id();
286       }
287       else {
288         $properties[$export_name] = $this->get($property_name);
289       }
290     }
291
292     if (empty($this->third_party_settings)) {
293       unset($properties['third_party_settings']);
294     }
295     if (empty($this->_core)) {
296       unset($properties['_core']);
297     }
298     return $properties;
299   }
300
301   /**
302    * Gets the typed config manager.
303    *
304    * @return \Drupal\Core\Config\TypedConfigManagerInterface
305    */
306   protected function getTypedConfig() {
307     return \Drupal::service('config.typed');
308   }
309
310   /**
311    * {@inheritdoc}
312    */
313   public function preSave(EntityStorageInterface $storage) {
314     parent::preSave($storage);
315
316     if ($this instanceof EntityWithPluginCollectionInterface) {
317       // Any changes to the plugin configuration must be saved to the entity's
318       // copy as well.
319       foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) {
320         $this->set($plugin_config_key, $plugin_collection->getConfiguration());
321       }
322     }
323
324     // Ensure this entity's UUID does not exist with a different ID, regardless
325     // of whether it's new or updated.
326     $matching_entities = $storage->getQuery()
327       ->condition('uuid', $this->uuid())
328       ->execute();
329     $matched_entity = reset($matching_entities);
330     if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
331       throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this UUID is already used for '$matched_entity'");
332     }
333
334     // If this entity is not new, load the original entity for comparison.
335     if (!$this->isNew()) {
336       $original = $storage->loadUnchanged($this->getOriginalId());
337       // Ensure that the UUID cannot be changed for an existing entity.
338       if ($original && ($original->uuid() != $this->uuid())) {
339         throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this entity already exists with UUID '{$original->uuid()}'");
340       }
341     }
342     if (!$this->isSyncing()) {
343       // Ensure the correct dependencies are present. If the configuration is
344       // being written during a configuration synchronization then there is no
345       // need to recalculate the dependencies.
346       $this->calculateDependencies();
347     }
348   }
349
350   /**
351    * {@inheritdoc}
352    */
353   public function __sleep() {
354     $keys_to_unset = [];
355     if ($this instanceof EntityWithPluginCollectionInterface) {
356       $vars = get_object_vars($this);
357       foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) {
358         // Save any changes to the plugin configuration to the entity.
359         $this->set($plugin_config_key, $plugin_collection->getConfiguration());
360         // If the plugin collections are stored as properties on the entity,
361         // mark them to be unset.
362         $keys_to_unset += array_filter($vars, function ($value) use ($plugin_collection) {
363           return $plugin_collection === $value;
364         });
365       }
366     }
367
368     $vars = parent::__sleep();
369
370     if (!empty($keys_to_unset)) {
371       $vars = array_diff($vars, array_keys($keys_to_unset));
372     }
373     return $vars;
374   }
375
376   /**
377    * {@inheritdoc}
378    */
379   public function calculateDependencies() {
380     // All dependencies should be recalculated on every save apart from enforced
381     // dependencies. This ensures stale dependencies are never saved.
382     $this->dependencies = array_intersect_key($this->dependencies, ['enforced' => '']);
383     if ($this instanceof EntityWithPluginCollectionInterface) {
384       // Configuration entities need to depend on the providers of any plugins
385       // that they store the configuration for.
386       foreach ($this->getPluginCollections() as $plugin_collection) {
387         foreach ($plugin_collection as $instance) {
388           $this->calculatePluginDependencies($instance);
389         }
390       }
391     }
392     if ($this instanceof ThirdPartySettingsInterface) {
393       // Configuration entities need to depend on the providers of any third
394       // parties that they store the configuration for.
395       foreach ($this->getThirdPartyProviders() as $provider) {
396         $this->addDependency('module', $provider);
397       }
398     }
399     return $this;
400   }
401
402   /**
403    * {@inheritdoc}
404    */
405   public function urlInfo($rel = 'edit-form', array $options = []) {
406     // Unless language was already provided, avoid setting an explicit language.
407     $options += ['language' => NULL];
408     return parent::urlInfo($rel, $options);
409   }
410
411   /**
412    * {@inheritdoc}
413    */
414   public function url($rel = 'edit-form', $options = []) {
415     // Do not remove this override: the default value of $rel is different.
416     return parent::url($rel, $options);
417   }
418
419   /**
420    * {@inheritdoc}
421    */
422   public function link($text = NULL, $rel = 'edit-form', array $options = []) {
423     // Do not remove this override: the default value of $rel is different.
424     return parent::link($text, $rel, $options);
425   }
426
427   /**
428    * {@inheritdoc}
429    */
430   public function toUrl($rel = 'edit-form', array $options = []) {
431     // Unless language was already provided, avoid setting an explicit language.
432     $options += ['language' => NULL];
433     return parent::toUrl($rel, $options);
434   }
435
436   /**
437    * {@inheritdoc}
438    */
439   public function getCacheTagsToInvalidate() {
440     // Use cache tags that match the underlying config object's name.
441     // @see \Drupal\Core\Config\ConfigBase::getCacheTags()
442     return ['config:' . $this->getConfigDependencyName()];
443   }
444
445   /**
446    * Overrides \Drupal\Core\Entity\DependencyTrait:addDependency().
447    *
448    * Note that this function should only be called from implementations of
449    * \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies(),
450    * as dependencies are recalculated during every entity save.
451    *
452    * @see \Drupal\Core\Config\Entity\ConfigEntityDependency::hasDependency()
453    */
454   protected function addDependency($type, $name) {
455     // A config entity is always dependent on its provider. There is no need to
456     // explicitly declare the dependency. An explicit dependency on Core, which
457     // provides some plugins, is also not needed.
458     if ($type == 'module' && ($name == $this->getEntityType()->getProvider() || $name == 'core')) {
459       return $this;
460     }
461
462     return $this->addDependencyTrait($type, $name);
463   }
464
465   /**
466    * {@inheritdoc}
467    */
468   public function getDependencies() {
469     $dependencies = $this->dependencies;
470     if (isset($dependencies['enforced'])) {
471       // Merge the enforced dependencies into the list of dependencies.
472       $enforced_dependencies = $dependencies['enforced'];
473       unset($dependencies['enforced']);
474       $dependencies = NestedArray::mergeDeep($dependencies, $enforced_dependencies);
475     }
476     return $dependencies;
477   }
478
479   /**
480    * {@inheritdoc}
481    */
482   public function getConfigDependencyName() {
483     return $this->getEntityType()->getConfigPrefix() . '.' . $this->id();
484   }
485
486   /**
487    * {@inheritdoc}
488    */
489   public function getConfigTarget() {
490     // For configuration entities, use the config ID for the config target
491     // identifier. This ensures that default configuration (which does not yet
492     // have UUIDs) can be provided and installed with references to the target,
493     // and also makes config dependencies more readable.
494     return $this->id();
495   }
496
497   /**
498    * {@inheritdoc}
499    */
500   public function onDependencyRemoval(array $dependencies) {
501     $changed = FALSE;
502     if (!empty($this->third_party_settings)) {
503       $old_count = count($this->third_party_settings);
504       $this->third_party_settings = array_diff_key($this->third_party_settings, array_flip($dependencies['module']));
505       $changed = $old_count != count($this->third_party_settings);
506     }
507     return $changed;
508   }
509
510   /**
511    * {@inheritdoc}
512    *
513    * Override to never invalidate the entity's cache tag; the config system
514    * already invalidates it.
515    */
516   protected function invalidateTagsOnSave($update) {
517     Cache::invalidateTags($this->getEntityType()->getListCacheTags());
518   }
519
520   /**
521    * {@inheritdoc}
522    *
523    * Override to never invalidate the individual entities' cache tags; the
524    * config system already invalidates them.
525    */
526   protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
527     Cache::invalidateTags($entity_type->getListCacheTags());
528   }
529
530   /**
531    * {@inheritdoc}
532    */
533   public function setThirdPartySetting($module, $key, $value) {
534     $this->third_party_settings[$module][$key] = $value;
535     return $this;
536   }
537
538   /**
539    * {@inheritdoc}
540    */
541   public function getThirdPartySetting($module, $key, $default = NULL) {
542     if (isset($this->third_party_settings[$module][$key])) {
543       return $this->third_party_settings[$module][$key];
544     }
545     else {
546       return $default;
547     }
548   }
549
550   /**
551    * {@inheritdoc}
552    */
553   public function getThirdPartySettings($module) {
554     return isset($this->third_party_settings[$module]) ? $this->third_party_settings[$module] : [];
555   }
556
557   /**
558    * {@inheritdoc}
559    */
560   public function unsetThirdPartySetting($module, $key) {
561     unset($this->third_party_settings[$module][$key]);
562     // If the third party is no longer storing any information, completely
563     // remove the array holding the settings for this module.
564     if (empty($this->third_party_settings[$module])) {
565       unset($this->third_party_settings[$module]);
566     }
567     return $this;
568   }
569
570   /**
571    * {@inheritdoc}
572    */
573   public function getThirdPartyProviders() {
574     return array_keys($this->third_party_settings);
575   }
576
577   /**
578    * {@inheritdoc}
579    */
580   public static function preDelete(EntityStorageInterface $storage, array $entities) {
581     parent::preDelete($storage, $entities);
582
583     foreach ($entities as $entity) {
584       if ($entity->isUninstalling() || $entity->isSyncing()) {
585         // During extension uninstall and configuration synchronization
586         // deletions are already managed.
587         break;
588       }
589       // Fix or remove any dependencies.
590       $config_entities = static::getConfigManager()->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity->getConfigDependencyName()], FALSE);
591       /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent_entity */
592       foreach ($config_entities['update'] as $dependent_entity) {
593         $dependent_entity->save();
594       }
595       foreach ($config_entities['delete'] as $dependent_entity) {
596         $dependent_entity->delete();
597       }
598     }
599   }
600
601   /**
602    * Gets the configuration manager.
603    *
604    * @return \Drupal\Core\Config\ConfigManager
605    *   The configuration manager.
606    */
607   protected static function getConfigManager() {
608     return \Drupal::service('config.manager');
609   }
610
611   /**
612    * {@inheritdoc}
613    */
614   public function isInstallable() {
615     return TRUE;
616   }
617
618   /**
619    * {@inheritdoc}
620    */
621   public function trustData() {
622     $this->trustedData = TRUE;
623     return $this;
624   }
625
626   /**
627    * {@inheritdoc}
628    */
629   public function hasTrustedData() {
630     return $this->trustedData;
631   }
632
633   /**
634    * {@inheritdoc}
635    */
636   public function save() {
637     $return = parent::save();
638     $this->trustedData = FALSE;
639     return $return;
640   }
641
642 }