diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 6e0b4d8..ef25bb1 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -309,6 +309,9 @@ protected function urlRouteParameters($rel) { if ($rel === 'revision') { $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); } + if ($rel === 'uuid') { + $uri_route_parameters[$this->getEntityTypeId()] = $this->uuid(); + } return $uri_route_parameters; } diff --git a/core/lib/Drupal/Core/Entity/EntityTypeManager.php b/core/lib/Drupal/Core/Entity/EntityTypeManager.php index ab7f645..399963f 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeManager.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php @@ -7,6 +7,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\Entity\Exception\InvalidLinkTemplateException; +use Drupal\Core\Entity\Routing\EntityUuidRouteProviderInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; @@ -83,6 +84,12 @@ public function processDefinition(&$definition, $plugin_id) { /** @var \Drupal\Core\Entity\EntityTypeInterface $definition */ parent::processDefinition($definition, $plugin_id); + // Add the UUID link template if applicable. + $route_providers = $definition->getRouteProviderClasses(); + $has_uuid_route = isset($route_providers['html']) && in_array(EntityUuidRouteProviderInterface::class, class_implements($route_providers['html']), TRUE); + if ($definition->hasKey('uuid') && $definition->hasViewBuilderClass() && $has_uuid_route) { + $definition->setLinkTemplate('uuid', "/{$plugin_id}/{{$plugin_id}}"); + } // All link templates must have a leading slash. foreach ((array) $definition->getLinkTemplates() as $link_relation_name => $link_template) { if ($link_template[0] != '/') { diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php index b1f6abd..c0f1afa 100644 --- a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php @@ -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,12 +24,13 @@ * - add-form * - edit-form * - delete-form + * - uuid * * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider. * * @internal */ -class DefaultHtmlRouteProvider implements EntityRouteProviderInterface, EntityHandlerInterface { +class DefaultHtmlRouteProvider implements EntityUuidRouteProviderInterface, EntityHandlerInterface { /** * The entity type manager. @@ -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 so that the UUID pattern can 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); } @@ -230,6 +238,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_id = $entity_type->id(); + $route = new Route("/{$entity_type_id}/{{$entity_type_id}}"); + $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. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type diff --git a/core/lib/Drupal/Core/Entity/Routing/EntityUuidRouteProviderInterface.php b/core/lib/Drupal/Core/Entity/Routing/EntityUuidRouteProviderInterface.php new file mode 100644 index 0000000..704d834 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Routing/EntityUuidRouteProviderInterface.php @@ -0,0 +1,10 @@ +queryFactory->get('node'); - $query->condition('nid', $nids, 'IN'); + $group = $query->orConditionGroup() + ->condition('nid', $nids, 'IN') + ->condition('uuid', $nids, '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 @@ -151,9 +154,13 @@ public function checkNodeAccess(array $tree) { } $nids = $query->execute(); - foreach ($nids as $nid) { - foreach ($node_links[$nid] as $key => $link) { - $node_links[$nid][$key]->access = $access_result; + if ($nids) { + foreach ($nids as $nid) { + if (isset($node_links[$nid])) { + foreach ($node_links[$nid] as $key => $link) { + $node_links[$nid][$key]->access = $access_result; + } + } } } } @@ -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. diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php index 573cd49..0d93051 100644 --- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php +++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php @@ -2,6 +2,7 @@ namespace Drupal\Core\ParamConverter; +use Drupal\Component\Uuid\Uuid; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\TypedData\TranslatableInterface; @@ -62,6 +63,12 @@ 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) { diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 0f5b4e5..fca01f8 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -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; @@ -323,7 +324,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 @@ -338,7 +339,11 @@ 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/d44a0040-2844-4cca-b0b5-20c6c96c4d8c for loading the canonical path to node entity with id 1."); + } + if (Uuid::isValid($entity_id)) { + // UUID instead of entity ID. + return new static("entity.$entity_type_id.uuid", [$entity_type_id => $entity_id], $options); } return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options); diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module index 463f8ca..cab395f 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -201,8 +201,12 @@ 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', '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 +216,12 @@ 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', '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); diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php index 95d9007..093c6a3 100644 --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ b/core/modules/menu_ui/src/Tests/MenuTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Menu\MenuLinkInterface; use Drupal\Core\Url; use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\node\Entity\NodeType; use Drupal\system\Entity\Menu; use Drupal\node\Entity\Node; @@ -69,7 +70,7 @@ protected function setUp() { $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 +222,14 @@ function addCustomMenu() { // Enable the block. $block = $this->drupalPlaceBlock('system_menu_block:' . $menu_name); $this->blockPlacements[$menu_name] = $block->id(); + + // Make this menu available for node-edit forms. + /* @var \Drupal\node\NodeTypeInterface $type */ + $type = NodeType::load('article'); + $node_menus = $type->getThirdPartySetting('menu_ui', 'available_menus', array('main')); + $node_menus[] = $menu_name; + $type->setThirdPartySetting('menu_ui', 'available_menus', $node_menus); + $type->save(); return Menu::load($menu_name); } @@ -466,6 +475,18 @@ function doMenuTests() { // Save menu links for later tests. $this->items[] = $item1; $this->items[] = $item2; + + // Test links using node/{uuid}. + $node6 = $this->drupalCreateNode(array('type' => 'article')); + $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())); } /** diff --git a/core/modules/node/src/Entity/NodeRouteProvider.php b/core/modules/node/src/Entity/NodeRouteProvider.php index 0803f1e..327afbc 100644 --- a/core/modules/node/src/Entity/NodeRouteProvider.php +++ b/core/modules/node/src/Entity/NodeRouteProvider.php @@ -2,21 +2,33 @@ namespace Drupal\node\Entity; +use Drupal\Component\Uuid\Uuid; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\Routing\EntityRouteProviderInterface; +use Drupal\Core\Entity\Routing\EntityUuidRouteProviderInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; /** * Provides routes for nodes. */ -class NodeRouteProvider implements EntityRouteProviderInterface { +class NodeRouteProvider implements EntityUuidRouteProviderInterface { /** * {@inheritdoc} */ 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', diff --git a/core/modules/user/src/Entity/UserRouteProvider.php b/core/modules/user/src/Entity/UserRouteProvider.php index d1e9671..d6e3c0f 100644 --- a/core/modules/user/src/Entity/UserRouteProvider.php +++ b/core/modules/user/src/Entity/UserRouteProvider.php @@ -2,21 +2,33 @@ namespace Drupal\user\Entity; +use Drupal\Component\Uuid\Uuid; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\Routing\EntityRouteProviderInterface; +use Drupal\Core\Entity\Routing\EntityUuidRouteProviderInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; /** * Provides routes for the user entity. */ -class UserRouteProvider implements EntityRouteProviderInterface { +class UserRouteProvider implements EntityUuidRouteProviderInterface { /** * {@inheritdoc} */ 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', diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php index c2325d4..08ebd50 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php @@ -99,6 +99,9 @@ protected function setUpEntityTypeDefinitions($definitions = []) { // Give the entity type a legitimate class to return. $entity_type->getClass()->willReturn($class); + // Don't allow uuid key. + $entity_type->hasKey('uuid')->willReturn(FALSE); + $definitions[$key] = $entity_type->reveal(); } diff --git a/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php b/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php index 7196652..e3c1478 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Routing/DefaultHtmlRouteProviderTest.php @@ -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; @@ -253,6 +254,52 @@ public function providerTestGetCanonicalRoute() { } /** + * @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->id()->willReturn('the_entity_type_id'); + $entity_type3->getKey('uuid')->willReturn(TRUE); + $entity_type3->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_type3->reveal(), $route]; + return $data; + } + + /** * @covers ::getEntityTypeIdKeyType */ public function testGetEntityTypeIdKeyType() { @@ -313,5 +360,8 @@ public function getAddFormRoute(EntityTypeInterface $entity_type) { public function getCanonicalRoute(EntityTypeInterface $entity_type) { return parent::getCanonicalRoute($entity_type); } + public function getUuidRoute(EntityTypeInterface $entity_type) { + return parent::getUuidRoute($entity_type); + } } diff --git a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php index 2000fe6..24aa65f 100644 --- a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php @@ -5,6 +5,7 @@ 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 @@ public function testCheckNodeAccess() { 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,11 +291,23 @@ public function testCheckNodeAccess() { )); $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()) ->method('execute') diff --git a/core/tests/Drupal/Tests/Core/ParamConverter/EntityConverterTest.php b/core/tests/Drupal/Tests/Core/ParamConverter/EntityConverterTest.php index ded278e..2536fd3 100644 --- a/core/tests/Drupal/Tests/Core/ParamConverter/EntityConverterTest.php +++ b/core/tests/Drupal/Tests/Core/ParamConverter/EntityConverterTest.php @@ -88,6 +88,12 @@ public function testConvert($value, array $definition, array $defaults, $expecte ['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)); } @@ -103,6 +109,8 @@ public function providerTestConvert() { $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; } diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index 1b8f080..b05ae5c 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -570,6 +570,14 @@ public function providerTestEntityUris() { ['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, + ], ]; }