Commit 85bc69df authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers, tstoeckler:...

Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers, tstoeckler: Make it possible to link to an entity by UUID
parent c9fb29d5
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -309,6 +309,9 @@ protected function urlRouteParameters($rel) {
    if ($rel === 'revision' && $this instanceof RevisionableInterface) {
      $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
    }
    if ($rel === 'uuid') {
      $uri_route_parameters[$this->getEntityTypeId()] = $this->uuid();
    }

    return $uri_route_parameters;
  }
+36 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\Core\Entity\Routing;

use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\Controller\EntityController;
use Drupal\Core\Entity\EntityFieldManagerInterface;
@@ -23,6 +24,7 @@
 * - add-form
 * - edit-form
 * - delete-form
 * - uuid
 *
 * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider.
 *
@@ -83,6 +85,12 @@ public function getRoutes(EntityTypeInterface $entity_type) {
      $collection->add("entity.{$entity_type_id}.add_form", $add_form_route);
    }

    // This goes before canonical because the UUID pattern must be tested before
    // non-integer entity IDs.
    if ($uuid_route = $this->getUuidRoute($entity_type)) {
      $collection->add("entity.{$entity_type_id}.uuid", $uuid_route);
    }

    if ($canonical_route = $this->getCanonicalRoute($entity_type)) {
      $collection->add("entity.{$entity_type_id}.canonical", $canonical_route);
    }
@@ -229,6 +237,34 @@ protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    }
  }

  /**
   * Gets the UUID route.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type.
   *
   * @return \Symfony\Component\Routing\Route|null
   *   The generated route, if available.
   */
  protected function getUuidRoute(EntityTypeInterface $entity_type) {
    if ($entity_type->getKey('uuid') && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('uuid')) {
      $entity_type_id = $entity_type->id();
      $route = new Route($entity_type->getLinkTemplate('uuid'));
      $route
        ->addDefaults([
          '_entity_view' => $entity_type_id . '.full',
          '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
        ])
        ->setRequirement('_entity_access', $entity_type_id . '.view')
        ->setOption('parameters', [
          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
        ])
        // Set requirement for UUID pattern.
        ->setRequirement($entity_type_id, '^' . Uuid::VALID_PATTERN . '$');
      return $route;
    }
  }

  /**
   * Gets the edit-form route.
   *
+13 −6
Original line number Diff line number Diff line
@@ -132,10 +132,14 @@ public function checkNodeAccess(array $tree) {
    $node_links = array();
    $this->collectNodeLinks($tree, $node_links);
    if ($node_links) {
      $nids = array_keys($node_links);
      // These could be serial node IDs or UUIDs.
      $node_identifiers = array_keys($node_links);

      $query = $this->queryFactory->get('node');
      $query->condition('nid', $nids, 'IN');
      $group = $query->orConditionGroup()
        ->condition('nid', $node_identifiers, 'IN')
        ->condition('uuid', $node_identifiers, 'IN');
      $query->condition($group);

      // Allows admins to view all nodes, by both disabling node_access
      // query rewrite as well as not checking for the node status. The
@@ -150,13 +154,16 @@ public function checkNodeAccess(array $tree) {
        $query->condition('status', NODE_PUBLISHED);
      }

      $nids = $query->execute();
      // Cast to an array so we can loop, even if there are no results.
      $nids = (array) $query->execute();
      foreach ($nids as $nid) {
        if (isset($node_links[$nid])) {
          foreach ($node_links[$nid] as $key => $link) {
            $node_links[$nid][$key]->access = $access_result;
          }
        }
      }
    }

    return $tree;
  }
@@ -174,7 +181,7 @@ public function checkNodeAccess(array $tree) {
   */
  protected function collectNodeLinks(array &$tree, array &$node_links) {
    foreach ($tree as $key => &$element) {
      if ($element->link->getRouteName() == 'entity.node.canonical') {
      if (in_array($element->link->getRouteName(), ['entity.node.canonical', 'entity.node.uuid'], TRUE)) {
        $nid = $element->link->getRouteParameters()['node'];
        $node_links[$nid][$key] = $element;
        // Deny access by default. checkNodeAccess() will re-add it.
+10 −1
Original line number Diff line number Diff line
@@ -2,13 +2,14 @@

namespace Drupal\Core\ParamConverter;

use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Symfony\Component\Routing\Route;

/**
 * Parameter converter for upcasting entity IDs to full objects.
 * Parameter converter for upcasting entity IDs or UUIDs to full objects.
 *
 * This is useful in cases where the dynamic elements of the path can't be
 * auto-determined; for example, if your path refers to multiple of the same
@@ -57,11 +58,19 @@ public function __construct(EntityManagerInterface $entity_manager) {

  /**
   * {@inheritdoc}
   *
   * The value here can be either a serial entity ID, or the entity UUID.
   */
  public function convert($value, $definition, $name, array $defaults) {
    $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
    if ($storage = $this->entityManager->getStorage($entity_type_id)) {
      $entity = $storage->load($value);
      // If there is no entity loadable by ID, try to load by UUID.
      if (!$entity && Uuid::isValid($value)) {
        if ($entities = $storage->loadByProperties(['uuid' => $value])) {
          $entity = reset($entities);
        }
      }
      // If the entity type is translatable, ensure we return the proper
      // translation object for the current context.
      if ($entity instanceof EntityInterface && $entity instanceof TranslatableInterface) {
+9 −3
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
namespace Drupal\Core;

use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Uuid\Uuid;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
@@ -325,7 +326,7 @@ public static function fromUri($uri, $options = []) {
   *
   * @param array $uri_parts
   *   Parts from an URI of the form entity:{entity_type}/{entity_id} as from
   *   parse_url().
   *   parse_url(). Note that {entity_id} can be both a UUID and a serial ID.
   * @param array $options
   *   An array of options, see \Drupal\Core\Url::fromUri() for details.
   * @param string $uri
@@ -340,10 +341,15 @@ public static function fromUri($uri, $options = []) {
  protected static function fromEntityUri(array $uri_parts, array $options, $uri) {
    list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
    if ($uri_parts['scheme'] != 'entity' || $entity_id === '') {
      throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.");
      throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 or entity:node/{uuid} for loading the canonical path to node entity with id 1.");
    }
    $route_name = "entity.$entity_type_id.canonical";
    if (Uuid::isValid($entity_id)) {
      // UUID instead of entity ID.
      $route_name = "entity.$entity_type_id.uuid";
    }

    return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
    return new static($route_name, [$entity_type_id => $entity_id], $options);
  }

  /**
Loading