Issue #2293697 by Wim Leers, dawehner, Jo Fitzgerald, clemens.tolboom, vedpareek, tedbow, Munavijayalakshmi, hchonov, alexpott, effulgentsia, tstoeckler, Crell, klausi, EclipseGc: EntityResource POST routes all use the confusing default: use entity types' https://www.drupal.org/link-relations/create link template if available
parent
09096d7819
commit
4398109471
|
@ -1145,7 +1145,7 @@ services:
|
|||
class: Drupal\Core\Access\CsrfRequestHeaderAccessCheck
|
||||
arguments: ['@session_configuration', '@csrf_token']
|
||||
tags:
|
||||
- { name: access_check }
|
||||
- { name: access_check, needs_incoming_request: TRUE }
|
||||
maintenance_mode:
|
||||
class: Drupal\Core\Site\MaintenanceMode
|
||||
arguments: ['@state', '@current_user']
|
||||
|
|
|
@ -13,6 +13,7 @@ use Drupal\Core\Language\LanguageInterface;
|
|||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
|
||||
/**
|
||||
* Defines a base entity class.
|
||||
|
@ -323,7 +324,19 @@ abstract class Entity implements EntityInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function uriRelationships() {
|
||||
return array_keys($this->linkTemplates());
|
||||
return array_filter(array_keys($this->linkTemplates()), function ($link_relation_type) {
|
||||
// It's not guaranteed that every link relation type also has a
|
||||
// corresponding route. For some, additional modules or configuration may
|
||||
// be necessary. The interface demands that we only return supported URI
|
||||
// relationships.
|
||||
try {
|
||||
$this->toUrl($link_relation_type)->toString(TRUE)->getGeneratedUrl();
|
||||
}
|
||||
catch (RouteNotFoundException $e) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,6 +79,18 @@ class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
|
|||
return ['html'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a 4xx error for HTML.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function on4xx(GetResponseForExceptionEvent $event) {
|
||||
if (($exception = $event->getException()) && $exception instanceof HttpExceptionInterface) {
|
||||
$this->makeSubrequest($event, '/system/4xx', $exception->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a 401 error for HTML.
|
||||
*
|
||||
|
|
|
@ -42,6 +42,7 @@ use Drupal\user\UserInterface;
|
|||
* "delete-form" = "/block/{block_content}/delete",
|
||||
* "edit-form" = "/block/{block_content}",
|
||||
* "collection" = "/admin/structure/block/block-content",
|
||||
* "create" = "/block",
|
||||
* },
|
||||
* translatable = TRUE,
|
||||
* entity_keys = {
|
||||
|
|
|
@ -57,6 +57,7 @@ use Drupal\user\UserInterface;
|
|||
* "canonical" = "/comment/{comment}",
|
||||
* "delete-form" = "/comment/{comment}/delete",
|
||||
* "edit-form" = "/comment/{comment}/edit",
|
||||
* "create" = "/comment",
|
||||
* },
|
||||
* bundle_entity_type = "comment_type",
|
||||
* field_ui_base_route = "entity.comment_type.edit_form",
|
||||
|
|
|
@ -74,6 +74,7 @@ use Drupal\user\UserInterface;
|
|||
* "edit-form" = "/node/{node}/edit",
|
||||
* "version-history" = "/node/{node}/revisions",
|
||||
* "revision" = "/node/{node}/revisions/{node_revision}/view",
|
||||
* "create" = "/node",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
|
|
|
@ -31,3 +31,15 @@ services:
|
|||
arguments: ['@router.builder']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
rest.resource.entity.post_route.subscriber:
|
||||
class: \Drupal\rest\EventSubscriber\EntityResourcePostRouteSubscriber
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
|
||||
# @todo Remove in Drupal 9.0.0.
|
||||
rest.path_processor_entity_resource_bc:
|
||||
class: \Drupal\rest\PathProcessor\PathProcessorEntityResourceBC
|
||||
arguments: ['@entity_type.manager']
|
||||
tags:
|
||||
- { name: path_processor_inbound }
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\RouteBuildEvent;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Generates a 'create' route for an entity type if it has a REST POST route.
|
||||
*/
|
||||
class EntityResourcePostRouteSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The REST resource config storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $resourceConfigStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityResourcePostRouteSubscriber instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides routes on route rebuild time.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteBuildEvent $event
|
||||
* The route build event.
|
||||
*/
|
||||
public function onDynamicRouteEvent(RouteBuildEvent $event) {
|
||||
$route_collection = $event->getRouteCollection();
|
||||
|
||||
$resource_configs = $this->resourceConfigStorage->loadMultiple();
|
||||
// Iterate over all REST resource config entities.
|
||||
foreach ($resource_configs as $resource_config) {
|
||||
// We only care about REST resource config entities for the
|
||||
// \Drupal\rest\Plugin\rest\resource\EntityResource plugin.
|
||||
$plugin_id = $resource_config->toArray()['plugin_id'];
|
||||
if (substr($plugin_id, 0, 6) !== 'entity') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entity_type_id = substr($plugin_id, 7);
|
||||
$rest_post_route_name = "rest.entity.$entity_type_id.POST";
|
||||
if ($rest_post_route = $route_collection->get($rest_post_route_name)) {
|
||||
// Create a route for the 'create' link relation type for this entity
|
||||
// type that uses the same route definition as the REST 'POST' route
|
||||
// which use that entity type.
|
||||
// @see \Drupal\Core\Entity\Entity::toUrl()
|
||||
$entity_create_route_name = "entity.$entity_type_id.create";
|
||||
$route_collection->add($entity_create_route_name, $rest_post_route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
// Priority -10, to run after \Drupal\rest\Routing\ResourceRoutes, which has
|
||||
// priority 0.
|
||||
$events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent', -10];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest\PathProcessor;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Path processor to maintain BC for entity REST resource URLs from Drupal 8.0.
|
||||
*/
|
||||
class PathProcessorEntityResourceBC implements InboundPathProcessorInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a new PathProcessorEntityResourceBC instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processInbound($path, Request $request) {
|
||||
if ($request->getMethod() === 'POST' && strpos($path, '/entity/') === 0) {
|
||||
$parts = explode('/', $path);
|
||||
$entity_type_id = array_pop($parts);
|
||||
|
||||
// Until Drupal 8.3, no entity types specified a link template for the
|
||||
// 'create' link relation type. As of Drupal 8.3, all core content entity
|
||||
// types provide this link relation type. This inbound path processor
|
||||
// provides automatic backwards compatibility: it allows both the old
|
||||
// default from \Drupal\rest\Plugin\rest\resource\EntityResource, i.e.
|
||||
// "/entity/{entity_type}" and the link template specified in a particular
|
||||
// entity type. The former is rewritten to the latter
|
||||
// specific one if it exists.
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
if ($entity_type->hasLinkTemplate('create')) {
|
||||
return $entity_type->getLinkTemplate('create');
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
|
@ -74,7 +74,7 @@ class EntityDeriver implements ContainerDeriverInterface {
|
|||
|
||||
$default_uris = [
|
||||
'canonical' => "/entity/$entity_type_id/" . '{' . $entity_type_id . '}',
|
||||
'https://www.drupal.org/link-relations/create' => "/entity/$entity_type_id",
|
||||
'create' => "/entity/$entity_type_id",
|
||||
];
|
||||
|
||||
foreach ($default_uris as $link_relation => $default_uri) {
|
||||
|
|
|
@ -100,7 +100,15 @@ abstract class ResourceBase extends PluginBase implements ContainerFactoryPlugin
|
|||
|
||||
$definition = $this->getPluginDefinition();
|
||||
$canonical_path = isset($definition['uri_paths']['canonical']) ? $definition['uri_paths']['canonical'] : '/' . strtr($this->pluginId, ':', '/') . '/{id}';
|
||||
$create_path = isset($definition['uri_paths']['https://www.drupal.org/link-relations/create']) ? $definition['uri_paths']['https://www.drupal.org/link-relations/create'] : '/' . strtr($this->pluginId, ':', '/');
|
||||
$create_path = isset($definition['uri_paths']['create']) ? $definition['uri_paths']['create'] : '/' . strtr($this->pluginId, ':', '/');
|
||||
// BC: the REST module originally created the POST URL for a resource by
|
||||
// reading the 'https://www.drupal.org/link-relations/create' URI path from
|
||||
// the plugin annotation. For consistency with entity type definitions, that
|
||||
// then changed to reading the 'create' URI path. For any REST Resource
|
||||
// plugins that were using the old mechanism, we continue to support that.
|
||||
if (!isset($definition['uri_paths']['create']) && isset($definition['uri_paths']['https://www.drupal.org/link-relations/create'])) {
|
||||
$create_path = $definition['uri_paths']['https://www.drupal.org/link-relations/create'];
|
||||
}
|
||||
|
||||
$route_name = strtr($this->pluginId, ':', '.');
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
|
|||
* deriver = "Drupal\rest\Plugin\Deriver\EntityDeriver",
|
||||
* uri_paths = {
|
||||
* "canonical" = "/entity/{entity_type}/{entity}",
|
||||
* "https://www.drupal.org/link-relations/create" = "/entity/{entity_type}"
|
||||
* "create" = "/entity/{entity_type}"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
|
@ -431,7 +431,7 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
|
|||
* @see https://tools.ietf.org/html/rfc5988#section-5
|
||||
*/
|
||||
protected function addLinkHeaders(EntityInterface $entity, Response $response) {
|
||||
foreach ($entity->getEntityType()->getLinkTemplates() as $relation_name => $link_template) {
|
||||
foreach ($entity->uriRelationships() as $relation_name) {
|
||||
if ($this->linkRelationTypeManager->hasDefinition($relation_name)) {
|
||||
/** @var \Drupal\Core\Http\LinkRelationTypeInterface $link_relation_type */
|
||||
$link_relation_type = $this->linkRelationTypeManager->createInstance($relation_name);
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
namespace Drupal\rest\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Drupal\Core\Routing\RouteBuildEvent;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Drupal\rest\Plugin\Type\ResourcePluginManager;
|
||||
use Drupal\rest\RestResourceConfigInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Subscriber for REST-style routes.
|
||||
*/
|
||||
class ResourceRoutes extends RouteSubscriberBase {
|
||||
class ResourceRoutes implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The plugin manager for REST plugins.
|
||||
|
@ -54,18 +56,18 @@ class ResourceRoutes extends RouteSubscriberBase {
|
|||
/**
|
||||
* Alters existing routes for a specific collection.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $collection
|
||||
* The route collection for adding routes.
|
||||
* @param \Drupal\Core\Routing\RouteBuildEvent $event
|
||||
* The route build event.
|
||||
* @return array
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
public function onDynamicRouteEvent(RouteBuildEvent $event) {
|
||||
// Iterate over all enabled REST resource config entities.
|
||||
/** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
|
||||
$resource_configs = $this->resourceConfigStorage->loadMultiple();
|
||||
foreach ($resource_configs as $resource_config) {
|
||||
if ($resource_config->status()) {
|
||||
$resource_routes = $this->getRoutesForResourceConfig($resource_config);
|
||||
$collection->addCollection($resource_routes);
|
||||
$event->getRouteCollection()->addCollection($resource_routes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,4 +133,12 @@ class ResourceRoutes extends RouteSubscriberBase {
|
|||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[RoutingEvents::DYNAMIC] = 'onDynamicRouteEvent';
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -679,8 +679,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
// missing ?_format query string.
|
||||
$response = $this->request('POST', $url, $request_options);
|
||||
$this->assertSame(415, $response->getStatusCode());
|
||||
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertContains(htmlspecialchars('No "Content-Type" request header specified'), (string) $response->getBody());
|
||||
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertContains('A client error happened', (string) $response->getBody());
|
||||
|
||||
|
||||
$url->setOption('query', ['_format' => static::$format]);
|
||||
|
@ -814,6 +814,17 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
$this->assertSame([], $response->getHeader('Location'));
|
||||
}
|
||||
$this->assertFalse($response->hasHeader('X-Drupal-Cache'));
|
||||
|
||||
// BC: old default POST URLs have their path updated by the inbound path
|
||||
// processor \Drupal\rest\PathProcessor\PathProcessorEntityResourceBC to the
|
||||
// new URL, which is derived from the 'create' link template if an entity
|
||||
// type specifies it.
|
||||
if ($this->entity->getEntityType()->hasLinkTemplate('create')) {
|
||||
$this->entityStorage->load(static::$secondCreatedEntityId)->delete();
|
||||
$old_url = Url::fromUri('base:entity/' . static::$entityTypeId);
|
||||
$response = $this->request('POST', $old_url, $request_options);
|
||||
$this->assertResourceResponse(201, FALSE, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -851,7 +862,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
if ($has_canonical_url) {
|
||||
$this->assertSame(405, $response->getStatusCode());
|
||||
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
|
||||
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertContains('A client error happened', (string) $response->getBody());
|
||||
}
|
||||
else {
|
||||
$this->assertSame(404, $response->getStatusCode());
|
||||
|
@ -880,8 +892,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
// DX: 415 when no Content-Type request header.
|
||||
$response = $this->request('PATCH', $url, $request_options);
|
||||
$this->assertSame(415, $response->getStatusCode());
|
||||
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertTrue(FALSE !== strpos((string) $response->getBody(), htmlspecialchars('No "Content-Type" request header specified')));
|
||||
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertContains('A client error happened', (string) $response->getBody());
|
||||
|
||||
|
||||
$url->setOption('query', ['_format' => static::$format]);
|
||||
|
@ -1039,7 +1051,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
if ($has_canonical_url) {
|
||||
$this->assertSame(405, $response->getStatusCode());
|
||||
$this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow'));
|
||||
$this->assertSame(['text/plain; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
|
||||
$this->assertContains('A client error happened', (string) $response->getBody());
|
||||
}
|
||||
else {
|
||||
$this->assertSame(404, $response->getStatusCode());
|
||||
|
@ -1174,8 +1187,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
* The URL to POST to.
|
||||
*/
|
||||
protected function getEntityResourcePostUrl() {
|
||||
$has_canonical_url = $this->entity->hasLinkTemplate('https://www.drupal.org/link-relations/create');
|
||||
return $has_canonical_url ? $this->entity->toUrl() : Url::fromUri('base:entity/' . static::$entityTypeId);
|
||||
$has_create_url = $this->entity->hasLinkTemplate('create');
|
||||
return $has_create_url ? Url::fromUri('internal:' . $this->entity->getEntityType()->getLinkTemplate('create')) : Url::fromUri('base:entity/' . static::$entityTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -321,6 +321,9 @@ abstract class ResourceTestBase extends BrowserTestBase {
|
|||
* 'http_errors = FALSE' request option, nor do we want them to have to
|
||||
* convert Drupal Url objects to strings.
|
||||
*
|
||||
* We also don't want to follow redirects automatically, to ensure these tests
|
||||
* are able to detect when redirects are added or removed.
|
||||
*
|
||||
* @see \GuzzleHttp\ClientInterface::request()
|
||||
*
|
||||
* @param string $method
|
||||
|
@ -334,6 +337,7 @@ abstract class ResourceTestBase extends BrowserTestBase {
|
|||
*/
|
||||
protected function request($method, Url $url, array $request_options) {
|
||||
$request_options[RequestOptions::HTTP_ERRORS] = FALSE;
|
||||
$request_options[RequestOptions::ALLOW_REDIRECTS] = FALSE;
|
||||
$request_options = $this->decorateWithXdebugCookie($request_options);
|
||||
$client = $this->getSession()->getDriver()->getClient()->getClient();
|
||||
return $client->request($method, $url->setAbsolute(TRUE)->toString(), $request_options);
|
||||
|
|
|
@ -9,6 +9,18 @@ use Drupal\Core\Controller\ControllerBase;
|
|||
*/
|
||||
class Http4xxController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The default 4xx error content.
|
||||
*
|
||||
* @return array
|
||||
* A render array containing the message to display for 4xx errors.
|
||||
*/
|
||||
public function on4xx() {
|
||||
return [
|
||||
'#markup' => $this->t('A client error happened'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The default 401 content.
|
||||
*
|
||||
|
|
|
@ -22,6 +22,14 @@ system.404:
|
|||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
system.4xx:
|
||||
path: '/system/4xx'
|
||||
defaults:
|
||||
_controller: '\Drupal\system\Controller\Http4xxController:on4xx'
|
||||
_title: 'Client error'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
system.admin:
|
||||
path: '/admin'
|
||||
defaults:
|
||||
|
|
|
@ -48,6 +48,9 @@ use Drupal\user\UserInterface;
|
|||
* },
|
||||
* field_ui_base_route = "entity.entity_test.admin_form",
|
||||
* )
|
||||
*
|
||||
* Note that this entity type annotation intentionally omits the "create" link
|
||||
* template. See https://www.drupal.org/node/2293697.
|
||||
*/
|
||||
class EntityTest extends ContentEntityBase implements EntityOwnerInterface {
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
|
|||
* "add-form" = "/entity_test_with_bundle/add/{entity_test_bundle}",
|
||||
* "edit-form" = "/entity_test_with_bundle/{entity_test_with_bundle}/edit",
|
||||
* "delete-form" = "/entity_test_with_bundle/{entity_test_with_bundle}/delete",
|
||||
* "create" = "/entity_test_with_bundle",
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
|
|
|
@ -46,6 +46,7 @@ use Drupal\taxonomy\TermInterface;
|
|||
* "canonical" = "/taxonomy/term/{taxonomy_term}",
|
||||
* "delete-form" = "/taxonomy/term/{taxonomy_term}/delete",
|
||||
* "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
|
||||
* "create" = "/taxonomy/term",
|
||||
* },
|
||||
* permission_granularity = "bundle"
|
||||
* )
|
||||
|
|
Loading…
Reference in New Issue