4efcade7a16e6a8f5ac204ce8982cccb8017fe4a
[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     $id_key = $entity_type->getKey('id');
271     $property_names = $entity_type->getPropertiesToExport($this->id());
272     if (empty($property_names)) {
273       $config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
274       throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
275     }
276     foreach ($property_names as $property_name => $export_name) {
277       // Special handling for IDs so that computed compound IDs work.
278       // @see \Drupal\Core\Entity\EntityDisplayBase::id()
279       if ($property_name == $id_key) {
280         $properties[$export_name] = $this->id();
281       }
282       else {
283         $properties[$export_name] = $this->get($property_name);
284       }
285     }
286
287     if (empty($this->third_party_settings)) {
288       unset($properties['third_party_settings']);
289     }
290     if (empty($this->_core)) {
291       unset($properties['_core']);
292     }
293     return $properties;
294   }
295
296   /**
297    * Gets the typed config manager.
298    *
299    * @return \Drupal\Core\Config\TypedConfigManagerInterface
300    */
301   protected function getTypedConfig() {
302     return \Drupal::service('config.typed');
303   }
304
305   /**
306    * {@inheritdoc}
307    */
308   public function preSave(EntityStorageInterface $storage) {
309     parent::preSave($storage);
310
311     if ($this instanceof EntityWithPluginCollectionInterface) {
312       // Any changes to the plugin configuration must be saved to the entity's
313       // copy as well.
314       foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) {
315         $this->set($plugin_config_key, $plugin_collection->getConfiguration());
316       }
317     }
318
319     // Ensure this entity's UUID does not exist with a different ID, regardless
320     // of whether it's new or updated.
321     $matching_entities = $storage->getQuery()
322       ->condition('uuid', $this->uuid())
323       ->execute();
324     $matched_entity = reset($matching_entities);
325     if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
326       throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this UUID is already used for '$matched_entity'");
327     }
328
329     // If this entity is not new, load the original entity for comparison.
330     if (!$this->isNew()) {
331       $original = $storage->loadUnchanged($this->getOriginalId());
332       // Ensure that the UUID cannot be changed for an existing entity.
333       if ($original && ($original->uuid() != $this->uuid())) {
334         throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this entity already exists with UUID '{$original->uuid()}'");
335       }
336     }
337     if (!$this->isSyncing()) {
338       // Ensure the correct dependencies are present. If the configuration is
339       // being written during a configuration synchronization then there is no
340       // need to recalculate the dependencies.
341       $this->calculateDependencies();
342     }
343   }
344
345   /**
346    * {@inheritdoc}
347    */
348   public function __sleep() {
349     $keys_to_unset = [];
350     if ($this instanceof EntityWithPluginCollectionInterface) {
351       $vars = get_object_vars($this);
352       foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) {
353         // Save any changes to the plugin configuration to the entity.
354         $this->set($plugin_config_key, $plugin_collection->getConfiguration());
355         // If the plugin collections are stored as properties on the entity,
356         // mark them to be unset.
357         $keys_to_unset += array_filter($vars, function ($value) use ($plugin_collection) {
358           return $plugin_collection === $value;
359         });
360       }
361     }
362
363     $vars = parent::__sleep();
364
365     if (!empty($keys_to_unset)) {
366       $vars = array_diff($vars, array_keys($keys_to_unset));
367     }
368     return $vars;
369   }
370
371   /**
372    * {@inheritdoc}
373    */
374   public function calculateDependencies() {
375     // All dependencies should be recalculated on every save apart from enforced
376     // dependencies. This ensures stale dependencies are never saved.
377     $this->dependencies = array_intersect_key($this->dependencies, ['enforced' => '']);
378     if ($this instanceof EntityWithPluginCollectionInterface) {
379       // Configuration entities need to depend on the providers of any plugins
380       // that they store the configuration for.
381       foreach ($this->getPluginCollections() as $plugin_collection) {
382         foreach ($plugin_collection as $instance) {
383           $this->calculatePluginDependencies($instance);
384         }
385       }
386     }
387     if ($this instanceof ThirdPartySettingsInterface) {
388       // Configuration entities need to depend on the providers of any third
389       // parties that they store the configuration for.
390       foreach ($this->getThirdPartyProviders() as $provider) {
391         $this->addDependency('module', $provider);
392       }
393     }
394     return $this;
395   }
396
397   /**
398    * {@inheritdoc}
399    */
400   public function urlInfo($rel = 'edit-form', array $options = []) {
401     // Unless language was already provided, avoid setting an explicit language.
402     $options += ['language' => NULL];
403     return parent::urlInfo($rel, $options);
404   }
405
406   /**
407    * {@inheritdoc}
408    */
409   public function url($rel = 'edit-form', $options = []) {
410     // Do not remove this override: the default value of $rel is different.
411     return parent::url($rel, $options);
412   }
413
414   /**
415    * {@inheritdoc}
416    */
417   public function link($text = NULL, $rel = 'edit-form', array $options = []) {
418     // Do not remove this override: the default value of $rel is different.
419     return parent::link($text, $rel, $options);
420   }
421
422   /**
423    * {@inheritdoc}
424    */
425   public function toUrl($rel = 'edit-form', array $options = []) {
426     // Unless language was already provided, avoid setting an explicit language.
427     $options += ['language' => NULL];
428     return parent::toUrl($rel, $options);
429   }
430
431   /**
432    * {@inheritdoc}
433    */
434   public function getCacheTagsToInvalidate() {
435     // Use cache tags that match the underlying config object's name.
436     // @see \Drupal\Core\Config\ConfigBase::getCacheTags()
437     return ['config:' . $this->getConfigDependencyName()];
438   }
439
440   /**
441    * Overrides \Drupal\Core\Entity\DependencyTrait:addDependency().
442    *
443    * Note that this function should only be called from implementations of
444    * \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies(),
445    * as dependencies are recalculated during every entity save.
446    *
447    * @see \Drupal\Core\Config\Entity\ConfigEntityDependency::hasDependency()
448    */
449   protected function addDependency($type, $name) {
450     // A config entity is always dependent on its provider. There is no need to
451     // explicitly declare the dependency. An explicit dependency on Core, which
452     // provides some plugins, is also not needed.
453     if ($type == 'module' && ($name == $this->getEntityType()->getProvider() || $name == 'core')) {
454       return $this;
455     }
456
457     return $this->addDependencyTrait($type, $name);
458   }
459
460   /**
461    * {@inheritdoc}
462    */
463   public function getDependencies() {
464     $dependencies = $this->dependencies;
465     if (isset($dependencies['enforced'])) {
466       // Merge the enforced dependencies into the list of dependencies.
467       $enforced_dependencies = $dependencies['enforced'];
468       unset($dependencies['enforced']);
469       $dependencies = NestedArray::mergeDeep($dependencies, $enforced_dependencies);
470     }
471     return $dependencies;
472   }
473
474   /**
475    * {@inheritdoc}
476    */
477   public function getConfigDependencyName() {
478     return $this->getEntityType()->getConfigPrefix() . '.' . $this->id();
479   }
480
481   /**
482    * {@inheritdoc}
483    */
484   public function getConfigTarget() {
485     // For configuration entities, use the config ID for the config target
486     // identifier. This ensures that default configuration (which does not yet
487     // have UUIDs) can be provided and installed with references to the target,
488     // and also makes config dependencies more readable.
489     return $this->id();
490   }
491
492   /**
493    * {@inheritdoc}
494    */
495   public function onDependencyRemoval(array $dependencies) {
496     $changed = FALSE;
497     if (!empty($this->third_party_settings)) {
498       $old_count = count($this->third_party_settings);
499       $this->third_party_settings = array_diff_key($this->third_party_settings, array_flip($dependencies['module']));
500       $changed = $old_count != count($this->third_party_settings);
501     }
502     return $changed;
503   }
504
505   /**
506    * {@inheritdoc}
507    *
508    * Override to never invalidate the entity's cache tag; the config system
509    * already invalidates it.
510    */
511   protected function invalidateTagsOnSave($update) {
512     Cache::invalidateTags($this->getEntityType()->getListCacheTags());
513   }
514
515   /**
516    * {@inheritdoc}
517    *
518    * Override to never invalidate the individual entities' cache tags; the
519    * config system already invalidates them.
520    */
521   protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
522     Cache::invalidateTags($entity_type->getListCacheTags());
523   }
524
525   /**
526    * {@inheritdoc}
527    */
528   public function setThirdPartySetting($module, $key, $value) {
529     $this->third_party_settings[$module][$key] = $value;
530     return $this;
531   }
532
533   /**
534    * {@inheritdoc}
535    */
536   public function getThirdPartySetting($module, $key, $default = NULL) {
537     if (isset($this->third_party_settings[$module][$key])) {
538       return $this->third_party_settings[$module][$key];
539     }
540     else {
541       return $default;
542     }
543   }
544
545   /**
546    * {@inheritdoc}
547    */
548   public function getThirdPartySettings($module) {
549     return isset($this->third_party_settings[$module]) ? $this->third_party_settings[$module] : [];
550   }
551
552   /**
553    * {@inheritdoc}
554    */
555   public function unsetThirdPartySetting($module, $key) {
556     unset($this->third_party_settings[$module][$key]);
557     // If the third party is no longer storing any information, completely
558     // remove the array holding the settings for this module.
559     if (empty($this->third_party_settings[$module])) {
560       unset($this->third_party_settings[$module]);
561     }
562     return $this;
563   }
564
565   /**
566    * {@inheritdoc}
567    */
568   public function getThirdPartyProviders() {
569     return array_keys($this->third_party_settings);
570   }
571
572   /**
573    * {@inheritdoc}
574    */
575   public static function preDelete(EntityStorageInterface $storage, array $entities) {
576     parent::preDelete($storage, $entities);
577
578     foreach ($entities as $entity) {
579       if ($entity->isUninstalling() || $entity->isSyncing()) {
580         // During extension uninstall and configuration synchronization
581         // deletions are already managed.
582         break;
583       }
584       // Fix or remove any dependencies.
585       $config_entities = static::getConfigManager()->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity->getConfigDependencyName()], FALSE);
586       /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent_entity */
587       foreach ($config_entities['update'] as $dependent_entity) {
588         $dependent_entity->save();
589       }
590       foreach ($config_entities['delete'] as $dependent_entity) {
591         $dependent_entity->delete();
592       }
593     }
594   }
595
596   /**
597    * Gets the configuration manager.
598    *
599    * @return \Drupal\Core\Config\ConfigManager
600    *   The configuration manager.
601    */
602   protected static function getConfigManager() {
603     return \Drupal::service('config.manager');
604   }
605
606   /**
607    * {@inheritdoc}
608    */
609   public function isInstallable() {
610     return TRUE;
611   }
612
613   /**
614    * {@inheritdoc}
615    */
616   public function trustData() {
617     $this->trustedData = TRUE;
618     return $this;
619   }
620
621   /**
622    * {@inheritdoc}
623    */
624   public function hasTrustedData() {
625     return $this->trustedData;
626   }
627
628   /**
629    * {@inheritdoc}
630    */
631   public function save() {
632     $return = parent::save();
633     $this->trustedData = FALSE;
634     return $return;
635   }
636
637 }