Issue #2353611 by larowlan, dawehner, sime, alexpott, Wim Leers, tstoeckler: Make it possible to link to an entity by UUID
parent
c9fb29d5ea
commit
85bc69df87
|
@ -309,6 +309,9 @@ abstract class Entity implements EntityInterface {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -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 @@ use Symfony\Component\Routing\RouteCollection;
|
|||
* - add-form
|
||||
* - edit-form
|
||||
* - delete-form
|
||||
* - uuid
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider.
|
||||
*
|
||||
|
@ -83,6 +85,12 @@ class DefaultHtmlRouteProvider implements EntityRouteProviderInterface, EntityHa
|
|||
$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 @@ class DefaultHtmlRouteProvider implements EntityRouteProviderInterface, EntityHa
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
|
@ -132,10 +132,14 @@ class DefaultMenuLinkTreeManipulators {
|
|||
$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,10 +154,13 @@ class DefaultMenuLinkTreeManipulators {
|
|||
$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) {
|
||||
foreach ($node_links[$nid] as $key => $link) {
|
||||
$node_links[$nid][$key]->access = $access_result;
|
||||
if (isset($node_links[$nid])) {
|
||||
foreach ($node_links[$nid] as $key => $link) {
|
||||
$node_links[$nid][$key]->access = $access_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +181,7 @@ class DefaultMenuLinkTreeManipulators {
|
|||
*/
|
||||
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.
|
||||
|
|
|
@ -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 @@ class EntityConverter implements ParamConverterInterface {
|
|||
|
||||
/**
|
||||
* {@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) {
|
||||
|
|
|
@ -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 @@ class Url {
|
|||
*
|
||||
* @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 @@ class Url {
|
|||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,6 +31,7 @@ use Drupal\aggregator\FeedInterface;
|
|||
* },
|
||||
* links = {
|
||||
* "canonical" = "/aggregator/sources/{aggregator_feed}",
|
||||
* "uuid" = "/aggregator/sources/{aggregator_feed}",
|
||||
* "edit-form" = "/aggregator/sources/{aggregator_feed}/configure",
|
||||
* "delete-form" = "/aggregator/sources/{aggregator_feed}/delete",
|
||||
* },
|
||||
|
|
|
@ -201,8 +201,13 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
|
|||
// Give priority to the default menu
|
||||
$type_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', array('main'));
|
||||
if (in_array($menu_name, $type_menus)) {
|
||||
$query = \Drupal::entityQuery('menu_link_content')
|
||||
->condition('link.uri', 'node/' . $node->id())
|
||||
$query = \Drupal::entityQuery('menu_link_content');
|
||||
$group = $query->orConditionGroup()
|
||||
->condition('link.uri', 'entity:node/' . $node->id())
|
||||
->condition('link.uri', 'entity:node/' . $node->uuid())
|
||||
->condition('link.uri', 'internal:/node/' . $node->id())
|
||||
->condition('link.uri', 'internal:/node/' . $node->uuid());
|
||||
$query->condition($group)
|
||||
->condition('menu_name', $menu_name)
|
||||
->sort('id', 'ASC')
|
||||
->range(0, 1);
|
||||
|
@ -212,8 +217,13 @@ function menu_ui_get_menu_link_defaults(NodeInterface $node) {
|
|||
}
|
||||
// Check all allowed menus if a link does not exist in the default menu.
|
||||
if (!$id && !empty($type_menus)) {
|
||||
$query = \Drupal::entityQuery('menu_link_content')
|
||||
$query = \Drupal::entityQuery('menu_link_content');
|
||||
$group = $query->orConditionGroup()
|
||||
->condition('link.uri', 'entity:node/' . $node->id())
|
||||
->condition('link.uri', 'entity:node/' . $node->uuid())
|
||||
->condition('link.uri', 'internal:/node/' . $node->id())
|
||||
->condition('link.uri', 'internal:/node/' . $node->uuid());
|
||||
$query->condition($group)
|
||||
->condition('menu_name', array_values($type_menus), 'IN')
|
||||
->sort('id', 'ASC')
|
||||
->range(0, 1);
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
namespace Drupal\menu_ui\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\menu_link_content\Entity\MenuLinkContent;
|
||||
use Drupal\node\Entity\Node;
|
||||
|
@ -12,7 +13,7 @@ use Drupal\node\Entity\Node;
|
|||
*
|
||||
* @group menu_ui
|
||||
*/
|
||||
class MenuNodeTest extends WebTestBase {
|
||||
class MenuNodeTest extends MenuWebTestBase {
|
||||
|
||||
/**
|
||||
* An editor user.
|
||||
|
@ -338,4 +339,38 @@ class MenuNodeTest extends WebTestBase {
|
|||
$this->assertFieldById('edit-menu-title', $translated_node_title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding links to nodes using the /node/{uuid} format.
|
||||
*/
|
||||
public function testNodeUuidLink() {
|
||||
/* @var \Drupal\node\NodeTypeInterface $type */
|
||||
$type = NodeType::load('page');
|
||||
// Enable the main menu for this node type..
|
||||
$menu_name = 'main';
|
||||
$type->setThirdPartySetting('menu_ui', 'available_menus', [$menu_name]);
|
||||
$type->save();
|
||||
// Test links using node/{uuid}.
|
||||
$node6 = $this->drupalCreateNode(array('type' => 'page'));
|
||||
$uuid_link = $this->addMenuLink('', '/node/' . $node6->uuid(), $menu_name);
|
||||
$this->verifyMenuLink($uuid_link, $node6);
|
||||
$this->drupalGet($node6->url('edit-form'));
|
||||
$this->assertFieldByName('menu[title]', $uuid_link->label());
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
\Drupal::entityManager()->getStorage('menu_link_content')->resetCache([$uuid_link->id()]);
|
||||
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $uuid_link */
|
||||
$uuid_link = MenuLinkContent::load($uuid_link->id());
|
||||
$this->assertEqual($uuid_link->getUrlObject(), Url::fromUri('internal:/node/' . $node6->uuid()));
|
||||
// Test with entity:node/{uuid}.
|
||||
$node7 = $this->drupalCreateNode(array('type' => 'page'));
|
||||
$uuid_link = $this->addMenuLink('', 'entity:node/' . $node7->uuid(), $menu_name);
|
||||
$this->verifyMenuLink($uuid_link, $node7);
|
||||
$this->drupalGet($node7->url('edit-form'));
|
||||
$this->assertFieldByName('menu[title]', $uuid_link->label());
|
||||
$this->drupalPostForm(NULL, [], t('Save'));
|
||||
\Drupal::entityManager()->getStorage('menu_link_content')->resetCache([$uuid_link->id()]);
|
||||
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $uuid_link */
|
||||
$uuid_link = MenuLinkContent::load($uuid_link->id());
|
||||
$this->assertEqual($uuid_link->getUrlObject(), Url::fromUri('entity:node/' . $node7->uuid()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ class MenuTest extends MenuWebTestBase {
|
|||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
|
||||
// Create users.
|
||||
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer blocks', 'administer menu', 'create article content'));
|
||||
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer blocks', 'administer menu', 'create article content', 'edit any article content'));
|
||||
$this->authenticatedUser = $this->drupalCreateUser(array());
|
||||
}
|
||||
|
||||
|
@ -221,6 +221,7 @@ class MenuTest extends MenuWebTestBase {
|
|||
// Enable the block.
|
||||
$block = $this->drupalPlaceBlock('system_menu_block:' . $menu_name);
|
||||
$this->blockPlacements[$menu_name] = $block->id();
|
||||
|
||||
return Menu::load($menu_name);
|
||||
}
|
||||
|
||||
|
@ -582,55 +583,6 @@ class MenuTest extends MenuWebTestBase {
|
|||
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="entitymenuedit-form"><a href="' . base_path() . 'admin/structure/menu/manage/' . $custom_menu->id() . '">Edit menu</a></li></ul>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a menu link using the UI.
|
||||
*
|
||||
* @param string $parent
|
||||
* Optional parent menu link id.
|
||||
* @param string $path
|
||||
* The path to enter on the form. Defaults to the front page.
|
||||
* @param string $menu_name
|
||||
* Menu name. Defaults to 'tools'.
|
||||
* @param bool $expanded
|
||||
* Whether or not this menu link is expanded. Setting this to TRUE should
|
||||
* test whether it works when we do the authenticatedUser tests. Defaults
|
||||
* to FALSE.
|
||||
* @param string $weight
|
||||
* Menu weight. Defaults to 0.
|
||||
*
|
||||
* @return \Drupal\menu_link_content\Entity\MenuLinkContent
|
||||
* A menu link entity.
|
||||
*/
|
||||
function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded = FALSE, $weight = '0') {
|
||||
// View add menu link page.
|
||||
$this->drupalGet("admin/structure/menu/manage/$menu_name/add");
|
||||
$this->assertResponse(200);
|
||||
|
||||
$title = '!link_' . $this->randomMachineName(16);
|
||||
$edit = array(
|
||||
'link[0][uri]' => $path,
|
||||
'title[0][value]' => $title,
|
||||
'description[0][value]' => '',
|
||||
'enabled[value]' => 1,
|
||||
'expanded[value]' => $expanded,
|
||||
'menu_parent' => $menu_name . ':' . $parent,
|
||||
'weight[0][value]' => $weight,
|
||||
);
|
||||
|
||||
// Add menu link.
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertText('The menu link has been saved.');
|
||||
|
||||
$menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title));
|
||||
|
||||
$menu_link = reset($menu_links);
|
||||
$this->assertTrue($menu_link, 'Menu link was found in database.');
|
||||
$this->assertMenuLink($menu_link->getPluginId(), array('menu_name' => $menu_name, 'children' => array(), 'parent' => $parent));
|
||||
|
||||
return $menu_link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to add menu link with invalid path or no access permission.
|
||||
*/
|
||||
|
@ -687,45 +639,6 @@ class MenuTest extends MenuWebTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a menu link using the UI.
|
||||
*
|
||||
* @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
|
||||
* Menu link.
|
||||
* @param object $item_node
|
||||
* Menu link content node.
|
||||
* @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent
|
||||
* Parent menu link.
|
||||
* @param object $parent_node
|
||||
* Parent menu link content node.
|
||||
*/
|
||||
function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) {
|
||||
// View home page.
|
||||
$this->drupalGet('');
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Verify parent menu link.
|
||||
if (isset($parent)) {
|
||||
// Verify menu link.
|
||||
$title = $parent->getTitle();
|
||||
$this->assertLink($title, 0, 'Parent menu link was displayed');
|
||||
|
||||
// Verify menu link link.
|
||||
$this->clickLink($title);
|
||||
$title = $parent_node->label();
|
||||
$this->assertTitle(t("@title | Drupal", array('@title' => $title)), 'Parent menu link link target was correct');
|
||||
}
|
||||
|
||||
// Verify menu link.
|
||||
$title = $item->getTitle();
|
||||
$this->assertLink($title, 0, 'Menu link was displayed');
|
||||
|
||||
// Verify menu link link.
|
||||
$this->clickLink($title);
|
||||
$title = $item_node->label();
|
||||
$this->assertTitle(t("@title | Drupal", array('@title' => $title)), 'Menu link link target was correct');
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the parent of a menu link using the UI.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\menu_ui\Tests;
|
||||
|
||||
use Drupal\menu_link_content\Entity\MenuLinkContent;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
|
@ -74,4 +75,97 @@ abstract class MenuWebTestBase extends WebTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a menu link using the UI.
|
||||
*
|
||||
* @param string $parent
|
||||
* Optional parent menu link id.
|
||||
* @param string $path
|
||||
* The path to enter on the form. Defaults to the front page.
|
||||
* @param string $menu_name
|
||||
* Menu name. Defaults to 'tools'.
|
||||
* @param bool $expanded
|
||||
* Whether or not this menu link is expanded. Setting this to TRUE should
|
||||
* test whether it works when we do the authenticatedUser tests. Defaults
|
||||
* to FALSE.
|
||||
* @param string $weight
|
||||
* Menu weight. Defaults to 0.
|
||||
*
|
||||
* @return \Drupal\menu_link_content\Entity\MenuLinkContent
|
||||
* A menu link entity.
|
||||
*/
|
||||
public function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded = FALSE, $weight = '0') {
|
||||
// View add menu link page.
|
||||
$this->drupalGet("admin/structure/menu/manage/$menu_name/add");
|
||||
$this->assertResponse(200);
|
||||
|
||||
$title = '!link_' . $this->randomMachineName(16);
|
||||
$edit = array(
|
||||
'link[0][uri]' => $path,
|
||||
'title[0][value]' => $title,
|
||||
'description[0][value]' => '',
|
||||
'enabled[value]' => 1,
|
||||
'expanded[value]' => $expanded,
|
||||
'menu_parent' => $menu_name . ':' . $parent,
|
||||
'weight[0][value]' => $weight,
|
||||
);
|
||||
|
||||
// Add menu link.
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertText('The menu link has been saved.');
|
||||
|
||||
$menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title));
|
||||
|
||||
$menu_link = reset($menu_links);
|
||||
$this->assertTrue($menu_link, 'Menu link was found in database.');
|
||||
$this->assertMenuLink($menu_link->getPluginId(), [
|
||||
'menu_name' => $menu_name,
|
||||
'children' => [],
|
||||
'parent' => $parent,
|
||||
]);
|
||||
|
||||
return $menu_link;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies a menu link using the UI.
|
||||
*
|
||||
* @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
|
||||
* Menu link.
|
||||
* @param object $item_node
|
||||
* Menu link content node.
|
||||
* @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent
|
||||
* Parent menu link.
|
||||
* @param object $parent_node
|
||||
* Parent menu link content node.
|
||||
*/
|
||||
public function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) {
|
||||
// View home page.
|
||||
$this->drupalGet('');
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Verify parent menu link.
|
||||
if (isset($parent)) {
|
||||
// Verify menu link.
|
||||
$title = $parent->getTitle();
|
||||
$this->assertLink($title, 0, 'Parent menu link was displayed');
|
||||
|
||||
// Verify menu link link.
|
||||
$this->clickLink($title);
|
||||
$title = $parent_node->label();
|
||||
$this->assertTitle(t("@title | Drupal", array('@title' => $title)), 'Parent menu link link target was correct');
|
||||
}
|
||||
|
||||
// Verify menu link.
|
||||
$title = $item->getTitle();
|
||||
$this->assertLink($title, 0, 'Menu link was displayed');
|
||||
|
||||
// Verify menu link link.
|
||||
$this->clickLink($title);
|
||||
$title = $item_node->label();
|
||||
$this->assertTitle(t("@title | Drupal", array('@title' => $title)), 'Menu link link target was correct');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ use Drupal\user\UserInterface;
|
|||
* permission_granularity = "bundle",
|
||||
* links = {
|
||||
* "canonical" = "/node/{node}",
|
||||
* "uuid" = "/node/{node}",
|
||||
* "delete-form" = "/node/{node}/delete",
|
||||
* "edit-form" = "/node/{node}/edit",
|
||||
* "version-history" = "/node/{node}/revisions",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\node\Entity;
|
||||
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
@ -17,6 +18,17 @@ class NodeRouteProvider implements EntityRouteProviderInterface {
|
|||
*/
|
||||
public function getRoutes( EntityTypeInterface $entity_type) {
|
||||
$route_collection = new RouteCollection();
|
||||
|
||||
$route = (new Route("/node/{node}"))
|
||||
->addDefaults([
|
||||
'_controller' => '\Drupal\node\Controller\NodeViewController::view',
|
||||
'_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
|
||||
])
|
||||
// Set requirement for UUID pattern.
|
||||
->setRequirement('node', '^' . Uuid::VALID_PATTERN . '$')
|
||||
->setRequirement('_entity_access', 'node.view');
|
||||
$route_collection->add('entity.node.uuid', $route);
|
||||
|
||||
$route = (new Route('/node/{node}'))
|
||||
->addDefaults([
|
||||
'_controller' => '\Drupal\node\Controller\NodeViewController::view',
|
||||
|
|
|
@ -1663,3 +1663,19 @@ function system_update_8014() {
|
|||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-rc".
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.2.0
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The simple presence of this update function clears cached entity definitions.
|
||||
*/
|
||||
function system_update_8200() {
|
||||
// Many core entity-types now have a UUID link template and route.
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.2.0".
|
||||
*/
|
||||
|
|
|
@ -42,6 +42,7 @@ use Drupal\user\UserInterface;
|
|||
* },
|
||||
* links = {
|
||||
* "canonical" = "/entity_test/{entity_test}",
|
||||
* "uuid" = "/entity_test/{entity_test}",
|
||||
* "add-form" = "/entity_test/add",
|
||||
* "edit-form" = "/entity_test/manage/{entity_test}/edit",
|
||||
* "delete-form" = "/entity_test/delete/entity_test/{entity_test}",
|
||||
|
|
|
@ -33,6 +33,7 @@ use Drupal\entity_test\FieldStorageDefinition;
|
|||
* },
|
||||
* links = {
|
||||
* "canonical" = "/entity_test_base_field_display/{entity_test_base_field_display}/edit",
|
||||
* "uuid" = "/entity_test_base_field_display/{entity_test_base_field_display}/edit",
|
||||
* "add-form" = "/entity_test_base_field_display/add",
|
||||
* "edit-form" = "/entity_test_base_field_display/manage/{entity_test_base_field_display}",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_base_field_display/{entity_test_base_field_display}/edit",
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace Drupal\entity_test\Entity;
|
|||
* "add-page" = "/entity_test_mul/add",
|
||||
* "add-form" = "/entity_test_mul/add/{type}",
|
||||
* "canonical" = "/entity_test_mul/manage/{entity_test_mul}",
|
||||
* "uuid" = "/entity_test_mul/manage/{entity_test_mul}",
|
||||
* "edit-form" = "/entity_test_mul/manage/{entity_test_mul}/edit",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_mul/{entity_test_mul}",
|
||||
* },
|
||||
|
|
|
@ -39,6 +39,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
* links = {
|
||||
* "add-form" = "/entity_test_mul_changed/add",
|
||||
* "canonical" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
|
||||
* "uuid" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
|
||||
* "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}/edit",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_mul_changed/{entity_test_mul_changed}",
|
||||
* },
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace Drupal\entity_test\Entity;
|
|||
* links = {
|
||||
* "add-form" = "/entity_test_mul_langcode_key/add",
|
||||
* "canonical" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
|
||||
* "uuid" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
|
||||
* "edit-form" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}/edit",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_mul_langcode_key/{entity_test_mul_langcode_key}",
|
||||
* },
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace Drupal\entity_test\Entity;
|
|||
* links = {
|
||||
* "add-form" = "/entity_test_mulrev/add",
|
||||
* "canonical" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
|
||||
* "uuid" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_mulrev/{entity_test_mulrev}",
|
||||
* "edit-form" = "/entity_test_mulrev/manage/{entity_test_mulrev}/edit",
|
||||
* "revision" = "/entity_test_mulrev/{entity_test_mulrev}/revision/{entity_test_mulrev_revision}/view",
|
||||
|
|
|
@ -40,6 +40,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
* links = {
|
||||
* "add-form" = "/entity_test_mulrev_changed/add",
|
||||
* "canonical" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
|
||||
* "uuid" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_mulrev_changed/{entity_test_mulrev_changed}",
|
||||
* "edit-form" = "/entity_test_mulrev_changed/manage/{entity_test_mulrev_changed}/edit",
|
||||
* "revision" = "/entity_test_mulrev_changed/{entity_test_mulrev_changed}/revision/{entity_test_mulrev_changed_revision}/view",
|
||||
|
|
|
@ -39,6 +39,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
* links = {
|
||||
* "add-form" = "/entity_test_rev/add",
|
||||
* "canonical" = "/entity_test_rev/manage/{entity_test_rev}",
|
||||
* "uuid" = "/entity_test_rev/manage/{entity_test_rev}",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_rev/{entity_test_rev}",
|
||||
* "edit-form" = "/entity_test_rev/manage/{entity_test_rev}/edit",
|
||||
* "revision" = "/entity_test_rev/{entity_test_rev}/revision/{entity_test_rev_revision}/view",
|
||||
|
|
|
@ -29,6 +29,7 @@ use Drupal\Core\Entity\EntityTypeInterface;
|
|||
* },
|
||||
* links = {
|
||||
* "canonical" = "/entity_test_string_id/manage/{entity_test_string_id}",
|
||||
* "uuid" = "/entity_test_string_id/manage/{entity_test_string_id}",
|
||||
* "add-form" = "/entity_test_string_id/add",
|
||||
* "edit-form" = "/entity_test_string_id/manage/{entity_test_string_id}",
|
||||
* },
|
||||
|
|
|
@ -37,6 +37,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
* bundle_entity_type = "entity_test_bundle",
|
||||
* links = {
|
||||
* "canonical" = "/entity_test_with_bundle/{entity_test_with_bundle}",
|
||||
* "uuid" = "/entity_test_with_bundle/{entity_test_with_bundle}",
|
||||
* "add-page" = "/entity_test_with_bundle/add",
|
||||
* "add-form" = "/entity_test_with_bundle/add/{entity_test_bundle}",
|
||||
* "edit-form" = "/entity_test_with_bundle/{entity_test_with_bundle}/edit",
|
||||
|
|
|
@ -37,6 +37,7 @@ use Drupal\Core\Entity\RevisionableContentEntityBase;
|
|||
* },
|
||||
* links = {
|
||||
* "canonical" = "/entity_test_revlog/manage/{entity_test_revlog}",
|
||||
* "uuid" = "/entity_test_revlog/manage/{entity_test_revlog}",
|
||||
* "delete-form" = "/entity_test/delete/entity_test_revlog/{entity_test_revlog}",
|
||||
* "edit-form" = "/entity_test_revlog/manage/{entity_test_revlog}/edit",
|
||||
* "revision" = "/entity_test_revlog/{entity_test_revlog}/revision/{entity_test_revlog_revision}/view",
|
||||
|
|
|
@ -44,6 +44,7 @@ use Drupal\taxonomy\TermInterface;
|
|||
* common_reference_target = TRUE,
|
||||
* links = {
|
||||
* "canonical" = "/taxonomy/term/{taxonomy_term}",
|
||||
* "uuid" = "/taxonomy/term/{taxonomy_term}",
|
||||
* "delete-form" = "/taxonomy/term/{taxonomy_term}/delete",
|
||||
* "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
|
||||
* },
|
||||
|
|
|
@ -76,6 +76,16 @@ entity.taxonomy_vocabulary.overview_form:
|
|||
requirements:
|
||||
_entity_access: 'taxonomy_vocabulary.view'
|
||||
|
||||
entity.taxonomy_term.uuid:
|
||||
path: '/taxonomy/term/{taxonomy_term}'
|
||||
defaults:
|
||||
_entity_view: 'taxonomy_term.full'
|
||||
_title: 'Taxonomy term'
|
||||
_title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::termTitle'
|
||||
requirements:
|
||||
_entity_access: 'taxonomy_term.view'
|
||||
taxonomy_term: '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'
|
||||
|
||||
entity.taxonomy_term.canonical:
|
||||
path: '/taxonomy/term/{taxonomy_term}'
|
||||
defaults:
|
||||
|
|
|
@ -48,6 +48,7 @@ use Drupal\user\UserInterface;
|
|||
* },
|
||||
* links = {
|
||||
* "canonical" = "/user/{user}",
|
||||
* "uuid" = "/user/{user}",
|
||||
* "edit-form" = "/user/{user}/edit",
|
||||
* "cancel-form" = "/user/{user}/cancel",
|
||||
* "collection" = "/admin/people",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\user\Entity;
|
||||
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
@ -17,6 +18,17 @@ class UserRouteProvider implements EntityRouteProviderInterface {
|
|||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
$route_collection = new RouteCollection();
|
||||
|
||||
$route = (new Route("/user/{user}"))
|
||||
->addDefaults([
|
||||
'_entity_view' => 'user.full',
|
||||
'_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
|
||||
])
|
||||
// Set requirement for UUID pattern.
|
||||
->setRequirement('user', '^' . Uuid::VALID_PATTERN . '$')
|
||||
->setRequirement('_entity_access', 'user.view');
|
||||
$route_collection->add('entity.user.uuid', $route);
|
||||
|
||||
$route = (new Route('/user/{user}'))
|
||||
->setDefaults([
|
||||
'_entity_view' => 'user.full',
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Tests\Core\Entity\Routing;
|
||||
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
@ -252,6 +253,59 @@ class DefaultHtmlRouteProviderTest extends UnitTestCase {
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getUuidRoute
|
||||
* @dataProvider providerTestGetUuidRoute
|
||||
*/
|
||||
public function testGetUuidRoute(EntityTypeInterface $entity_type, Route $expected = NULL) {
|
||||
$route = $this->routeProvider->getUuidRoute($entity_type);
|
||||
$this->assertEquals($expected, $route);
|
||||
}
|
||||
|
||||
public function providerTestGetUuidRoute() {
|
||||
$data = [];
|
||||
|
||||
$entity_type1 = $this->getEntityType();
|
||||
$entity_type1->getKey('uuid')->willReturn(FALSE);
|
||||
$data['no_canonical_link_template'] = [$entity_type1->reveal()];
|
||||
|
||||
$entity_type2 = $this->getEntityType();;
|
||||
$entity_type2->getKey('uuid')->willReturn(TRUE);
|
||||
$entity_type2->hasViewBuilderClass()->willReturn(FALSE);
|
||||
$data['no_view_builder'] = [$entity_type2->reveal()];
|
||||
|
||||
$entity_type3 = $this->getEntityType($entity_type2);;
|
||||
$entity_type3->hasViewBuilderClass()->willReturn(TRUE);
|
||||
$entity_type3->hasLinkTemplate('uuid')->willReturn(FALSE);
|
||||
$data['no_uuid_link_template'] = [$entity_type2->reveal()];
|
||||
|
||||
$entity_type4 = $this->getEntityType($entity_type2);
|
||||
$entity_type4->hasViewBuilderClass()->willReturn(TRUE);
|
||||
$entity_type4->id()->willReturn('the_entity_type_id');
|
||||
$entity_type4->getKey('uuid')->willReturn(TRUE);
|
||||
$entity_type4->hasLinkTemplate('uuid')->willReturn(TRUE);
|
||||
$entity_type4->getLinkTemplate('uuid')->willReturn('/the_entity_type_id/{the_entity_type_id}');
|
||||
$entity_type4->isSubclassOf(FieldableEntityInterface::class)->willReturn(FALSE);
|
||||
$route = (new Route('/the_entity_type_id/{the_entity_type_id}'))
|
||||
->setDefaults([
|
||||
'_entity_view' => 'the_entity_type_id.full',
|
||||
'_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
|
||||
])
|
||||
->setRequirements([
|
||||
'_entity_access' => 'the_entity_type_id.view',
|
||||
'the_entity_type_id' => '^' . Uuid::VALID_PATTERN . '$',
|
||||
])
|
||||
->setOptions([
|
||||
'parameters' => [
|
||||
'the_entity_type_id' => [
|
||||
'type' => 'entity:the_entity_type_id',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$data['has_uuid_route'] = [$entity_type4->reveal(), $route];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getEntityTypeIdKeyType
|
||||
*/
|
||||
|
@ -313,5 +367,8 @@ class TestDefaultHtmlRouteProvider extends DefaultHtmlRouteProvider {
|
|||
public function getCanonicalRoute(EntityTypeInterface $entity_type) {
|
||||
return parent::getCanonicalRoute($entity_type);
|
||||
}
|
||||
public function getUuidRoute(EntityTypeInterface $entity_type) {
|
||||
return parent::getUuidRoute($entity_type);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Drupal\Tests\Core\Menu;
|
|||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Core\DependencyInjection\Container;
|
||||
use Drupal\Core\Entity\Query\ConditionInterface;
|
||||
use Drupal\Core\Menu\DefaultMenuLinkTreeManipulators;
|
||||
use Drupal\Core\Menu\MenuLinkTreeElement;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
@ -274,7 +275,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
|
|||
1 => MenuLinkMock::create(array('id' => 'node.1', 'route_name' => 'entity.node.canonical', 'title' => 'foo', 'parent' => '', 'route_parameters' => array('node' => 1))),
|
||||
2 => MenuLinkMock::create(array('id' => 'node.2', 'route_name' => 'entity.node.canonical', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('node' => 2))),
|
||||
3 => MenuLinkMock::create(array('id' => 'node.3', 'route_name' => 'entity.node.canonical', 'title' => 'baz', 'parent' => 'node.2', 'route_parameters' => array('node' => 3))),
|
||||
4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => 4))),
|
||||
4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.uuid', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => 4))),
|
||||
5 => MenuLinkMock::create(array('id' => 'test.1', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => '')),
|
||||
6 => MenuLinkMock::create(array('id' => 'test.2', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => 'test.1')),
|
||||
);
|
||||
|
@ -290,10 +291,22 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
|
|||
));
|
||||
|
||||
$query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
|
||||
$condition = $this->getMock(ConditionInterface::class);
|
||||
$query->expects($this->at(0))
|
||||
->method('orConditionGroup')
|
||||
->willReturn($condition);
|
||||
$condition->expects($this->at(0))
|
||||
->method('condition')
|
||||
->with('nid', array(1, 2, 3, 4));
|
||||
->with('nid', array(1, 2, 3, 4))
|
||||
->willReturn($condition);
|
||||
$condition->expects($this->at(1))
|
||||
->method('condition')
|
||||
->with('uuid', array(1, 2, 3, 4))
|
||||
->willReturn($condition);
|
||||
$query->expects($this->at(1))
|
||||
->method('condition')
|
||||
->with($condition);
|
||||
$query->expects($this->at(2))
|
||||
->method('condition')
|
||||
->with('status', NODE_PUBLISHED);
|
||||
$query->expects($this->once())
|
||||
|
|
|
@ -89,6 +89,12 @@ class EntityConverterTest extends UnitTestCase {
|
|||
['valid_id', (object) ['id' => 'valid_id']],
|
||||
['invalid_id', NULL],
|
||||
]);
|
||||
$entity_storage->expects($this->any())
|
||||
->method('loadByProperties')
|
||||
->willReturnMap([
|
||||
[['uuid' => 'invalid_id'], NULL],
|
||||
[['uuid' => $value], [(object) ['uuid' => $value, 'id' => 'valid_id']]],
|
||||
]);
|
||||
|
||||
$this->assertEquals($expected_result, $this->entityConverter->convert($value, $definition, 'foo', $defaults));
|
||||
}
|
||||
|
@ -104,6 +110,8 @@ class EntityConverterTest extends UnitTestCase {
|
|||
$data[] = ['invalid_id', ['type' => 'entity:entity_test'], ['foo' => 'invalid_id'], NULL];
|
||||
// Entity type placeholder.
|
||||
$data[] = ['valid_id', ['type' => 'entity:{entity_type}'], ['foo' => 'valid_id', 'entity_type' => 'entity_test'], (object) ['id' => 'valid_id']];
|
||||
// UUID.
|
||||
$data[] = ['1c5217f4-553c-40d8-8389-a3cc3529d79c', ['type' => 'entity:entity_test'], ['foo' => '1c5217f4-553c-40d8-8389-a3cc3529d79c'], (object) ['uuid' => '1c5217f4-553c-40d8-8389-a3cc3529d79c', 'id' => 'valid_id']];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
|
|
@ -582,6 +582,14 @@ class UrlTest extends UnitTestCase {
|
|||
['page' => '1', 'foo' => 'yes', 'focus' => 'no'],
|
||||
'top',
|
||||
],
|
||||
[
|
||||
'entity:test_entity/d44a0040-2844-4cca-b0b5-20c6c96c4d8c',
|
||||
['fragment' => ''],
|
||||
'entity.test_entity.uuid',
|
||||
['test_entity' => 'd44a0040-2844-4cca-b0b5-20c6c96c4d8c'],
|
||||
NULL,
|
||||
NULL,
|
||||
],
|
||||
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue