"Fossies" - the Fresh Open Source Software Archive

Member "drupal-9.1.0-rc1/core/lib/Drupal/Core/Entity/EntityRepository.php" (18 Nov 2020, 11103 Bytes) of package /linux/www/drupal-9.1.0-rc1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "EntityRepository.php" see the Fossies "Dox" file reference documentation.

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