PageRenderTime 26ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Entity/EntityRepository.php

http://github.com/drupal/drupal
PHP | 306 lines | 145 code | 45 blank | 116 comment | 28 complexity | c2e10d957c1f1d42b28046b5b1e284b5 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Core\Entity;
  3. use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
  4. use Drupal\Core\Language\LanguageInterface;
  5. use Drupal\Core\Language\LanguageManagerInterface;
  6. use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
  7. use Drupal\Core\TypedData\TranslatableInterface as TranslatableDataInterface;
  8. /**
  9. * Provides several mechanisms for retrieving entities.
  10. */
  11. class EntityRepository implements EntityRepositoryInterface {
  12. /**
  13. * The entity type manager.
  14. *
  15. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  16. */
  17. protected $entityTypeManager;
  18. /**
  19. * The language manager.
  20. *
  21. * @var \Drupal\Core\Language\LanguageManagerInterface
  22. */
  23. protected $languageManager;
  24. /**
  25. * The context repository service.
  26. *
  27. * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
  28. */
  29. protected $contextRepository;
  30. /**
  31. * Constructs a new EntityRepository.
  32. *
  33. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  34. * The entity type manager.
  35. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  36. * The language manager.
  37. * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
  38. * The context repository service.
  39. */
  40. public function __construct(EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, ContextRepositoryInterface $context_repository = NULL) {
  41. $this->entityTypeManager = $entity_type_manager;
  42. $this->languageManager = $language_manager;
  43. if (isset($context_repository)) {
  44. $this->contextRepository = $context_repository;
  45. }
  46. else {
  47. @trigger_error('The context.repository service must be passed to EntityRepository::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2938929.', E_USER_DEPRECATED);
  48. $this->contextRepository = \Drupal::service('context.repository');
  49. }
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. public function loadEntityByUuid($entity_type_id, $uuid) {
  55. $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
  56. if (!$uuid_key = $entity_type->getKey('uuid')) {
  57. throw new EntityStorageException("Entity type $entity_type_id does not support UUIDs.");
  58. }
  59. $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadByProperties([$uuid_key => $uuid]);
  60. return ($entities) ? reset($entities) : NULL;
  61. }
  62. /**
  63. * {@inheritdoc}
  64. */
  65. public function loadEntityByConfigTarget($entity_type_id, $target) {
  66. $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
  67. // For configuration entities, the config target is given by the entity ID.
  68. // @todo Consider adding a method to allow entity types to indicate the
  69. // target identifier key rather than hard-coding this check. Issue:
  70. // https://www.drupal.org/node/2412983.
  71. if ($entity_type instanceof ConfigEntityTypeInterface) {
  72. $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($target);
  73. }
  74. // For content entities, the config target is given by the UUID.
  75. else {
  76. $entity = $this->loadEntityByUuid($entity_type_id, $target);
  77. }
  78. return $entity;
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. public function getTranslationFromContext(EntityInterface $entity, $langcode = NULL, $context = []) {
  84. $translation = $entity;
  85. if ($entity instanceof TranslatableDataInterface && count($entity->getTranslationLanguages()) > 1) {
  86. if (empty($langcode)) {
  87. $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
  88. $entity->addCacheContexts(['languages:' . LanguageInterface::TYPE_CONTENT]);
  89. }
  90. // Retrieve language fallback candidates to perform the entity language
  91. // negotiation, unless the current translation is already the desired one.
  92. if ($entity->language()->getId() != $langcode) {
  93. $context['data'] = $entity;
  94. $context += ['operation' => 'entity_view', 'langcode' => $langcode];
  95. $candidates = $this->languageManager->getFallbackCandidates($context);
  96. // Ensure the default language has the proper language code.
  97. $default_language = $entity->getUntranslated()->language();
  98. $candidates[$default_language->getId()] = LanguageInterface::LANGCODE_DEFAULT;
  99. // Return the most fitting entity translation.
  100. foreach ($candidates as $candidate) {
  101. if ($entity->hasTranslation($candidate)) {
  102. $translation = $entity->getTranslation($candidate);
  103. break;
  104. }
  105. }
  106. }
  107. }
  108. return $translation;
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public function getActive($entity_type_id, $entity_id, array $contexts = NULL) {
  114. return current($this->getActiveMultiple($entity_type_id, [$entity_id], $contexts)) ?: NULL;
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function getActiveMultiple($entity_type_id, array $entity_ids, array $contexts = NULL) {
  120. $active = [];
  121. if (!isset($contexts)) {
  122. $contexts = $this->contextRepository->getAvailableContexts();
  123. }
  124. // @todo Consider implementing a more performant version of this logic fully
  125. // supporting multiple entities in https://www.drupal.org/node/3031082.
  126. $langcode = $this->languageManager->isMultilingual()
  127. ? $this->getContentLanguageFromContexts($contexts)
  128. : $this->languageManager->getDefaultLanguage()->getId();
  129. $entities = $this->entityTypeManager
  130. ->getStorage($entity_type_id)
  131. ->loadMultiple($entity_ids);
  132. foreach ($entities as $id => $entity) {
  133. // Retrieve the fittest revision, if needed.
  134. if ($entity instanceof RevisionableInterface && $entity->getEntityType()->isRevisionable()) {
  135. $entity = $this->getLatestTranslationAffectedRevision($entity, $langcode);
  136. }
  137. // Retrieve the fittest translation, if needed.
  138. if ($entity instanceof TranslatableInterface) {
  139. $entity = $this->getTranslationFromContext($entity, $langcode);
  140. }
  141. $active[$id] = $entity;
  142. }
  143. return $active;
  144. }
  145. /**
  146. * {@inheritdoc}
  147. */
  148. public function getCanonical($entity_type_id, $entity_id, array $contexts = NULL) {
  149. return current($this->getCanonicalMultiple($entity_type_id, [$entity_id], $contexts)) ?: NULL;
  150. }
  151. /**
  152. * {@inheritdoc}
  153. */
  154. public function getCanonicalMultiple($entity_type_id, array $entity_ids, array $contexts = NULL) {
  155. $entities = $this->entityTypeManager->getStorage($entity_type_id)
  156. ->loadMultiple($entity_ids);
  157. if (!$entities || !$this->languageManager->isMultilingual()) {
  158. return $entities;
  159. }
  160. if (!isset($contexts)) {
  161. $contexts = $this->contextRepository->getAvailableContexts();
  162. }
  163. // @todo Consider deprecating the legacy context operation altogether in
  164. // https://www.drupal.org/node/3031124.
  165. $legacy_context = [];
  166. $key = static::CONTEXT_ID_LEGACY_CONTEXT_OPERATION;
  167. if (isset($contexts[$key])) {
  168. $legacy_context['operation'] = $contexts[$key]->getContextValue();
  169. }
  170. $canonical = [];
  171. $langcode = $this->getContentLanguageFromContexts($contexts);
  172. foreach ($entities as $id => $entity) {
  173. $canonical[$id] = $this->getTranslationFromContext($entity, $langcode, $legacy_context);
  174. }
  175. return $canonical;
  176. }
  177. /**
  178. * Retrieves the current content language from the specified contexts.
  179. *
  180. * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
  181. * An array of context items.
  182. *
  183. * @return string|null
  184. * A language code or NULL if no language context was provided.
  185. */
  186. protected function getContentLanguageFromContexts(array $contexts) {
  187. // Content language might not be configurable, in which case we need to fall
  188. // back to a configurable language type.
  189. foreach ([LanguageInterface::TYPE_CONTENT, LanguageInterface::TYPE_INTERFACE] as $language_type) {
  190. $context_id = '@language.current_language_context:' . $language_type;
  191. if (isset($contexts[$context_id])) {
  192. return $contexts[$context_id]->getContextValue()->getId();
  193. }
  194. }
  195. return $this->languageManager->getDefaultLanguage()->getId();
  196. }
  197. /**
  198. * Returns the latest revision translation of the specified entity.
  199. *
  200. * @param \Drupal\Core\Entity\RevisionableInterface $entity
  201. * The default revision of the entity being converted.
  202. * @param string $langcode
  203. * The language of the revision translation to be loaded.
  204. *
  205. * @return \Drupal\Core\Entity\RevisionableInterface
  206. * The latest translation-affecting revision for the specified entity, or
  207. * just the latest revision, if the specified entity is not translatable or
  208. * does not have a matching translation yet.
  209. */
  210. protected function getLatestTranslationAffectedRevision(RevisionableInterface $entity, $langcode) {
  211. $revision = NULL;
  212. $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
  213. if ($entity instanceof TranslatableRevisionableInterface && $entity->isTranslatable()) {
  214. /** @var \Drupal\Core\Entity\TranslatableRevisionableStorageInterface $storage */
  215. $revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
  216. // If the latest translation-affecting revision was a default revision, it
  217. // is fine to load the latest revision instead, because in this case the
  218. // latest revision, regardless of it being default or pending, will always
  219. // contain the most up-to-date values for the specified translation. This
  220. // provides a BC behavior when the route is defined by a module always
  221. // expecting the latest revision to be loaded and to be the default
  222. // revision. In this particular case the latest revision is always going
  223. // to be the default revision, since pending revisions would not be
  224. // supported.
  225. $revision = $revision_id ? $this->loadRevision($entity, $revision_id) : NULL;
  226. if (!$revision || ($revision->wasDefaultRevision() && !$revision->isDefaultRevision())) {
  227. $revision = NULL;
  228. }
  229. }
  230. // Fall back to the latest revisions if no affected revision for the current
  231. // content language could be found. This is acceptable as it means the
  232. // entity is not translated. This is the correct logic also on monolingual
  233. // sites.
  234. if (!isset($revision)) {
  235. $revision_id = $storage->getLatestRevisionId($entity->id());
  236. $revision = $this->loadRevision($entity, $revision_id);
  237. }
  238. return $revision;
  239. }
  240. /**
  241. * Loads the specified entity revision.
  242. *
  243. * @param \Drupal\Core\Entity\RevisionableInterface $entity
  244. * The default revision of the entity being converted.
  245. * @param string $revision_id
  246. * The identifier of the revision to be loaded.
  247. *
  248. * @return \Drupal\Core\Entity\RevisionableInterface
  249. * An entity revision object.
  250. */
  251. protected function loadRevision(RevisionableInterface $entity, $revision_id) {
  252. // We explicitly perform a loose equality check, since a revision ID may be
  253. // returned as an integer or a string.
  254. if ($entity->getLoadedRevisionId() != $revision_id) {
  255. /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
  256. $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
  257. return $storage->loadRevision($revision_id);
  258. }
  259. return $entity;
  260. }
  261. }