Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Entity.php
1 <?php
2
3 namespace Drupal\Core\Entity;
4
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
7 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
8 use Drupal\Component\Utility\Unicode;
9 use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
10 use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
11 use Drupal\Core\Language\Language;
12 use Drupal\Core\Language\LanguageInterface;
13 use Drupal\Core\Link;
14 use Drupal\Core\Session\AccountInterface;
15 use Drupal\Core\Url;
16
17 /**
18  * Defines a base entity class.
19  */
20 abstract class Entity implements EntityInterface {
21
22   use RefinableCacheableDependencyTrait;
23
24   use DependencySerializationTrait {
25     __sleep as traitSleep;
26   }
27
28   /**
29    * The entity type.
30    *
31    * @var string
32    */
33   protected $entityTypeId;
34
35   /**
36    * Boolean indicating whether the entity should be forced to be new.
37    *
38    * @var bool
39    */
40   protected $enforceIsNew;
41
42   /**
43    * A typed data object wrapping this entity.
44    *
45    * @var \Drupal\Core\TypedData\ComplexDataInterface
46    */
47   protected $typedData;
48
49   /**
50    * Constructs an Entity object.
51    *
52    * @param array $values
53    *   An array of values to set, keyed by property name. If the entity type
54    *   has bundles, the bundle key has to be specified.
55    * @param string $entity_type
56    *   The type of the entity to create.
57    */
58   public function __construct(array $values, $entity_type) {
59     $this->entityTypeId = $entity_type;
60     // Set initial values.
61     foreach ($values as $key => $value) {
62       $this->$key = $value;
63     }
64   }
65
66   /**
67    * Gets the entity manager.
68    *
69    * @return \Drupal\Core\Entity\EntityManagerInterface
70    *
71    * @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
72    *   Use \Drupal::entityTypeManager() instead in most cases. If the needed
73    *   method is not on \Drupal\Core\Entity\EntityTypeManagerInterface, see the
74    *   deprecated \Drupal\Core\Entity\EntityManager to find the
75    *   correct interface or service.
76    */
77   protected function entityManager() {
78     return \Drupal::entityManager();
79   }
80
81   /**
82    * Gets the entity type manager.
83    *
84    * @return \Drupal\Core\Entity\EntityTypeManagerInterface
85    */
86   protected function entityTypeManager() {
87     return \Drupal::entityTypeManager();
88   }
89
90   /**
91    * Gets the language manager.
92    *
93    * @return \Drupal\Core\Language\LanguageManagerInterface
94    */
95   protected function languageManager() {
96     return \Drupal::languageManager();
97   }
98
99   /**
100    * Gets the UUID generator.
101    *
102    * @return \Drupal\Component\Uuid\UuidInterface
103    */
104   protected function uuidGenerator() {
105     return \Drupal::service('uuid');
106   }
107
108   /**
109    * {@inheritdoc}
110    */
111   public function id() {
112     return isset($this->id) ? $this->id : NULL;
113   }
114
115   /**
116    * {@inheritdoc}
117    */
118   public function uuid() {
119     return isset($this->uuid) ? $this->uuid : NULL;
120   }
121
122   /**
123    * {@inheritdoc}
124    */
125   public function isNew() {
126     return !empty($this->enforceIsNew) || !$this->id();
127   }
128
129   /**
130    * {@inheritdoc}
131    */
132   public function enforceIsNew($value = TRUE) {
133     $this->enforceIsNew = $value;
134
135     return $this;
136   }
137
138   /**
139    * {@inheritdoc}
140    */
141   public function getEntityTypeId() {
142     return $this->entityTypeId;
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function bundle() {
149     return $this->entityTypeId;
150   }
151
152   /**
153    * {@inheritdoc}
154    */
155   public function label() {
156     $label = NULL;
157     $entity_type = $this->getEntityType();
158     if (($label_callback = $entity_type->getLabelCallback()) && is_callable($label_callback)) {
159       $label = call_user_func($label_callback, $this);
160     }
161     elseif (($label_key = $entity_type->getKey('label')) && isset($this->{$label_key})) {
162       $label = $this->{$label_key};
163     }
164     return $label;
165   }
166
167   /**
168    * {@inheritdoc}
169    */
170   public function urlInfo($rel = 'canonical', array $options = []) {
171     return $this->toUrl($rel, $options);
172   }
173
174   /**
175    * {@inheritdoc}
176    */
177   public function toUrl($rel = 'canonical', array $options = []) {
178     if ($this->id() === NULL) {
179       throw new EntityMalformedException(sprintf('The "%s" entity cannot have a URI as it does not have an ID', $this->getEntityTypeId()));
180     }
181
182     // The links array might contain URI templates set in annotations.
183     $link_templates = $this->linkTemplates();
184
185     // Links pointing to the current revision point to the actual entity. So
186     // instead of using the 'revision' link, use the 'canonical' link.
187     if ($rel === 'revision' && $this instanceof RevisionableInterface && $this->isDefaultRevision()) {
188       $rel = 'canonical';
189     }
190
191     if (isset($link_templates[$rel])) {
192       $route_parameters = $this->urlRouteParameters($rel);
193       $route_name = "entity.{$this->entityTypeId}." . str_replace(['-', 'drupal:'], ['_', ''], $rel);
194       $uri = new Url($route_name, $route_parameters);
195     }
196     else {
197       $bundle = $this->bundle();
198       // A bundle-specific callback takes precedence over the generic one for
199       // the entity type.
200       $bundles = $this->entityManager()->getBundleInfo($this->getEntityTypeId());
201       if (isset($bundles[$bundle]['uri_callback'])) {
202         $uri_callback = $bundles[$bundle]['uri_callback'];
203       }
204       elseif ($entity_uri_callback = $this->getEntityType()->getUriCallback()) {
205         $uri_callback = $entity_uri_callback;
206       }
207
208       // Invoke the callback to get the URI. If there is no callback, use the
209       // default URI format.
210       if (isset($uri_callback) && is_callable($uri_callback)) {
211         $uri = call_user_func($uri_callback, $this);
212       }
213       else {
214         throw new UndefinedLinkTemplateException("No link template '$rel' found for the '{$this->getEntityTypeId()}' entity type");
215       }
216     }
217
218     // Pass the entity data through as options, so that alter functions do not
219     // need to look up this entity again.
220     $uri
221       ->setOption('entity_type', $this->getEntityTypeId())
222       ->setOption('entity', $this);
223
224     // Display links by default based on the current language.
225     // Link relations that do not require an existing entity should not be
226     // affected by this entity's language, however.
227     if (!in_array($rel, ['collection', 'add-page', 'add-form'], TRUE)) {
228       $options += ['language' => $this->language()];
229     }
230
231     $uri_options = $uri->getOptions();
232     $uri_options += $options;
233
234     return $uri->setOptions($uri_options);
235   }
236
237   /**
238    * {@inheritdoc}
239    */
240   public function hasLinkTemplate($rel) {
241     $link_templates = $this->linkTemplates();
242     return isset($link_templates[$rel]);
243   }
244
245   /**
246    * Gets an array link templates.
247    *
248    * @return array
249    *   An array of link templates containing paths.
250    */
251   protected function linkTemplates() {
252     return $this->getEntityType()->getLinkTemplates();
253   }
254
255   /**
256    * {@inheritdoc}
257    */
258   public function link($text = NULL, $rel = 'canonical', array $options = []) {
259     return $this->toLink($text, $rel, $options)->toString();
260   }
261
262   /**
263    * {@inheritdoc}
264    */
265   public function toLink($text = NULL, $rel = 'canonical', array $options = []) {
266     if (!isset($text)) {
267       $text = $this->label();
268     }
269     $url = $this->toUrl($rel);
270     $options += $url->getOptions();
271     $url->setOptions($options);
272     return new Link($text, $url);
273   }
274
275   /**
276    * {@inheritdoc}
277    */
278   public function url($rel = 'canonical', $options = []) {
279     // While self::toUrl() will throw an exception if the entity has no id,
280     // the expected result for a URL is always a string.
281     if ($this->id() === NULL || !$this->hasLinkTemplate($rel)) {
282       return '';
283     }
284
285     $uri = $this->toUrl($rel);
286     $options += $uri->getOptions();
287     $uri->setOptions($options);
288     return $uri->toString();
289   }
290
291   /**
292    * Gets an array of placeholders for this entity.
293    *
294    * Individual entity classes may override this method to add additional
295    * placeholders if desired. If so, they should be sure to replicate the
296    * property caching logic.
297    *
298    * @param string $rel
299    *   The link relationship type, for example: canonical or edit-form.
300    *
301    * @return array
302    *   An array of URI placeholders.
303    */
304   protected function urlRouteParameters($rel) {
305     $uri_route_parameters = [];
306
307     if (!in_array($rel, ['collection', 'add-page', 'add-form'], TRUE)) {
308       // The entity ID is needed as a route parameter.
309       $uri_route_parameters[$this->getEntityTypeId()] = $this->id();
310     }
311     if ($rel === 'add-form' && ($this->getEntityType()->hasKey('bundle'))) {
312       $parameter_name = $this->getEntityType()->getBundleEntityType() ?: $this->getEntityType()->getKey('bundle');
313       $uri_route_parameters[$parameter_name] = $this->bundle();
314     }
315     if ($rel === 'revision' && $this instanceof RevisionableInterface) {
316       $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
317     }
318
319     return $uri_route_parameters;
320   }
321
322   /**
323    * {@inheritdoc}
324    */
325   public function uriRelationships() {
326     return array_keys($this->linkTemplates());
327   }
328
329   /**
330    * {@inheritdoc}
331    */
332   public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
333     if ($operation == 'create') {
334       return $this->entityManager()
335         ->getAccessControlHandler($this->entityTypeId)
336         ->createAccess($this->bundle(), $account, [], $return_as_object);
337     }
338     return $this->entityManager()
339       ->getAccessControlHandler($this->entityTypeId)
340       ->access($this, $operation, $account, $return_as_object);
341   }
342
343   /**
344    * {@inheritdoc}
345    */
346   public function language() {
347     if ($key = $this->getEntityType()->getKey('langcode')) {
348       $langcode = $this->$key;
349       $language = $this->languageManager()->getLanguage($langcode);
350       if ($language) {
351         return $language;
352       }
353     }
354     // Make sure we return a proper language object.
355     $langcode = !empty($this->langcode) ? $this->langcode : LanguageInterface::LANGCODE_NOT_SPECIFIED;
356     $language = new Language(['id' => $langcode]);
357     return $language;
358   }
359
360   /**
361    * {@inheritdoc}
362    */
363   public function save() {
364     return $this->entityManager()->getStorage($this->entityTypeId)->save($this);
365   }
366
367   /**
368    * {@inheritdoc}
369    */
370   public function delete() {
371     if (!$this->isNew()) {
372       $this->entityManager()->getStorage($this->entityTypeId)->delete([$this->id() => $this]);
373     }
374   }
375
376   /**
377    * {@inheritdoc}
378    */
379   public function createDuplicate() {
380     $duplicate = clone $this;
381     $entity_type = $this->getEntityType();
382     // Reset the entity ID and indicate that this is a new entity.
383     $duplicate->{$entity_type->getKey('id')} = NULL;
384     $duplicate->enforceIsNew();
385
386     // Check if the entity type supports UUIDs and generate a new one if so.
387     if ($entity_type->hasKey('uuid')) {
388       $duplicate->{$entity_type->getKey('uuid')} = $this->uuidGenerator()->generate();
389     }
390     return $duplicate;
391   }
392
393   /**
394    * {@inheritdoc}
395    */
396   public function getEntityType() {
397     return $this->entityManager()->getDefinition($this->getEntityTypeId());
398   }
399
400   /**
401    * {@inheritdoc}
402    */
403   public function preSave(EntityStorageInterface $storage) {
404     // Check if this is an entity bundle.
405     if ($this->getEntityType()->getBundleOf()) {
406       // Throw an exception if the bundle ID is longer than 32 characters.
407       if (Unicode::strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
408         throw new ConfigEntityIdLengthException("Attempt to create a bundle with an ID longer than " . EntityTypeInterface::BUNDLE_MAX_LENGTH . " characters: $this->id().");
409       }
410     }
411   }
412
413   /**
414    * {@inheritdoc}
415    */
416   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
417     $this->invalidateTagsOnSave($update);
418   }
419
420   /**
421    * {@inheritdoc}
422    */
423   public static function preCreate(EntityStorageInterface $storage, array &$values) {
424   }
425
426   /**
427    * {@inheritdoc}
428    */
429   public function postCreate(EntityStorageInterface $storage) {
430   }
431
432   /**
433    * {@inheritdoc}
434    */
435   public static function preDelete(EntityStorageInterface $storage, array $entities) {
436   }
437
438   /**
439    * {@inheritdoc}
440    */
441   public static function postDelete(EntityStorageInterface $storage, array $entities) {
442     static::invalidateTagsOnDelete($storage->getEntityType(), $entities);
443   }
444
445   /**
446    * {@inheritdoc}
447    */
448   public static function postLoad(EntityStorageInterface $storage, array &$entities) {
449   }
450
451   /**
452    * {@inheritdoc}
453    */
454   public function referencedEntities() {
455     return [];
456   }
457
458   /**
459    * {@inheritdoc}
460    */
461   public function getCacheContexts() {
462     return $this->cacheContexts;
463   }
464
465   /**
466    * {@inheritdoc}
467    */
468   public function getCacheTagsToInvalidate() {
469     // @todo Add bundle-specific listing cache tag?
470     //   https://www.drupal.org/node/2145751
471     if ($this->isNew()) {
472       return [];
473     }
474     return [$this->entityTypeId . ':' . $this->id()];
475   }
476
477   /**
478    * {@inheritdoc}
479    */
480   public function getCacheTags() {
481     if ($this->cacheTags) {
482       return Cache::mergeTags($this->getCacheTagsToInvalidate(), $this->cacheTags);
483     }
484     return $this->getCacheTagsToInvalidate();
485   }
486
487   /**
488    * {@inheritdoc}
489    */
490   public function getCacheMaxAge() {
491     return $this->cacheMaxAge;
492   }
493
494   /**
495    * {@inheritdoc}
496    */
497   public static function load($id) {
498     $entity_manager = \Drupal::entityManager();
499     return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->load($id);
500   }
501
502   /**
503    * {@inheritdoc}
504    */
505   public static function loadMultiple(array $ids = NULL) {
506     $entity_manager = \Drupal::entityManager();
507     return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->loadMultiple($ids);
508   }
509
510   /**
511    * {@inheritdoc}
512    */
513   public static function create(array $values = []) {
514     $entity_manager = \Drupal::entityManager();
515     return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->create($values);
516   }
517
518   /**
519    * Invalidates an entity's cache tags upon save.
520    *
521    * @param bool $update
522    *   TRUE if the entity has been updated, or FALSE if it has been inserted.
523    */
524   protected function invalidateTagsOnSave($update) {
525     // An entity was created or updated: invalidate its list cache tags. (An
526     // updated entity may start to appear in a listing because it now meets that
527     // listing's filtering requirements. A newly created entity may start to
528     // appear in listings because it did not exist before.)
529     $tags = $this->getEntityType()->getListCacheTags();
530     if ($this->hasLinkTemplate('canonical')) {
531       // Creating or updating an entity may change a cached 403 or 404 response.
532       $tags = Cache::mergeTags($tags, ['4xx-response']);
533     }
534     if ($update) {
535       // An existing entity was updated, also invalidate its unique cache tag.
536       $tags = Cache::mergeTags($tags, $this->getCacheTagsToInvalidate());
537     }
538     Cache::invalidateTags($tags);
539   }
540
541   /**
542    * Invalidates an entity's cache tags upon delete.
543    *
544    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
545    *   The entity type definition.
546    * @param \Drupal\Core\Entity\EntityInterface[] $entities
547    *   An array of entities.
548    */
549   protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
550     $tags = $entity_type->getListCacheTags();
551     foreach ($entities as $entity) {
552       // An entity was deleted: invalidate its own cache tag, but also its list
553       // cache tags. (A deleted entity may cause changes in a paged list on
554       // other pages than the one it's on. The one it's on is handled by its own
555       // cache tag, but subsequent list pages would not be invalidated, hence we
556       // must invalidate its list cache tags as well.)
557       $tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
558     }
559     Cache::invalidateTags($tags);
560   }
561
562   /**
563    * {@inheritdoc}
564    */
565   public function getOriginalId() {
566     // By default, entities do not support renames and do not have original IDs.
567     return NULL;
568   }
569
570   /**
571    * {@inheritdoc}
572    */
573   public function setOriginalId($id) {
574     // By default, entities do not support renames and do not have original IDs.
575     // If the specified ID is anything except NULL, this should mark this entity
576     // as no longer new.
577     if ($id !== NULL) {
578       $this->enforceIsNew(FALSE);
579     }
580
581     return $this;
582   }
583
584   /**
585    * {@inheritdoc}
586    */
587   public function toArray() {
588     return [];
589   }
590
591   /**
592    * {@inheritdoc}
593    */
594   public function getTypedData() {
595     if (!isset($this->typedData)) {
596       $class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
597       $this->typedData = $class::createFromEntity($this);
598     }
599     return $this->typedData;
600   }
601
602   /**
603    * {@inheritdoc}
604    */
605   public function __sleep() {
606     $this->typedData = NULL;
607     return $this->traitSleep();
608   }
609
610   /**
611    * {@inheritdoc}
612    */
613   public function getConfigDependencyKey() {
614     return $this->getEntityType()->getConfigDependencyKey();
615   }
616
617   /**
618    * {@inheritdoc}
619    */
620   public function getConfigDependencyName() {
621     return $this->getEntityTypeId() . ':' . $this->bundle() . ':' . $this->uuid();
622   }
623
624   /**
625    * {@inheritdoc}
626    */
627   public function getConfigTarget() {
628     // For content entities, use the UUID for the config target identifier.
629     // This ensures that references to the target can be deployed reliably.
630     return $this->uuid();
631   }
632
633 }