Commit 76bcb27b authored by alexpott's avatar alexpott

Issue #2167641 by tim.plunkett: EntityInterface::uri() should use route name and not path.

parent 87bff520
...@@ -207,8 +207,22 @@ public function preSave(EntityStorageControllerInterface $storage_controller) { ...@@ -207,8 +207,22 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function uri() { public function urlInfo($rel = 'edit-form') {
return parent::uri('edit-form'); return parent::urlInfo($rel);
}
/**
* {@inheritdoc}
*/
public function getSystemPath($rel = 'edit-form') {
return parent::getSystemPath($rel);
}
/**
* {@inheritdoc}
*/
public function url($rel = 'edit-form', $options = array()) {
return parent::url($rel, $options);
} }
} }
...@@ -32,31 +32,19 @@ public function load() { ...@@ -32,31 +32,19 @@ public function load() {
*/ */
public function getOperations(EntityInterface $entity) { public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity); $operations = parent::getOperations($entity);
$uri = $entity->uri();
// Ensure the edit operation exists since it is access controlled.
if (isset($operations['edit'])) {
// For configuration entities edit path is the MENU_DEFAULT_LOCAL_TASK and
// therefore should be accessed by the short route.
$operations['edit']['href'] = $uri['path'];
}
if ($this->entityType->hasKey('status')) { if ($this->entityType->hasKey('status')) {
if (!$entity->status()) { if (!$entity->status() && $entity->hasLinkTemplate('enable')) {
$operations['enable'] = array( $operations['enable'] = array(
'title' => t('Enable'), 'title' => t('Enable'),
'href' => $uri['path'] . '/enable',
'options' => $uri['options'],
'weight' => -10, 'weight' => -10,
); ) + $entity->urlInfo('enable');
} }
else { elseif ($entity->hasLinkTemplate('disable')) {
$operations['disable'] = array( $operations['disable'] = array(
'title' => t('Disable'), 'title' => t('Disable'),
'href' => $uri['path'] . '/disable',
'options' => $uri['options'],
'weight' => 40, 'weight' => 40,
); ) + $entity->urlInfo('disable');
} }
} }
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/** /**
* Defines a base entity class. * Defines a base entity class.
...@@ -38,18 +37,11 @@ abstract class Entity implements EntityInterface { ...@@ -38,18 +37,11 @@ abstract class Entity implements EntityInterface {
protected $enforceIsNew; protected $enforceIsNew;
/** /**
* The route provider service. * The URL generator.
* *
* @var \Drupal\Core\Routing\RouteProviderInterface * @var \Drupal\Core\Routing\UrlGeneratorInterface
*/ */
protected $routeProvider; protected $urlGenerator;
/**
* Local cache for URI placeholder substitution values.
*
* @var array
*/
protected $uriPlaceholderReplacements;
/** /**
* Constructs an Entity object. * Constructs an Entity object.
...@@ -127,99 +119,70 @@ public function label() { ...@@ -127,99 +119,70 @@ public function label() {
} }
/** /**
* Returns the URI elements of the entity. * {@inheritdoc}
*
* URI templates might be set in the links array in an annotation, for
* example:
* @code
* links = {
* "canonical" = "/node/{node}",
* "edit-form" = "/node/{node}/edit",
* "version-history" = "/node/{node}/revisions"
* }
* @endcode
* or specified in a callback function set like:
* @code
* uri_callback = "contact_category_uri",
* @endcode
* If the path is not set in the links array, the uri_callback function is
* used for setting the path. If this does not exist and the link relationship
* type is canonical, the path is set using the default template:
* entity/entityType/id.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
*
* @return array
* An array containing the 'path' and 'options' keys used to build the URI
* of the entity, and matching the signature of url().
*/ */
public function uri($rel = 'canonical') { public function urlInfo($rel = 'canonical') {
$entity_info = $this->getEntityType(); if ($this->isNew()) {
throw new EntityMalformedException(sprintf('The "%s" entity type has not been saved, and cannot have a URI.', $this->getEntityTypeId()));
}
// The links array might contain URI templates set in annotations. // The links array might contain URI templates set in annotations.
$link_templates = $this->linkTemplates(); $link_templates = $this->linkTemplates();
$template = NULL;
if (isset($link_templates[$rel])) { if (isset($link_templates[$rel])) {
try { // If there is a template for the given relationship type, generate the path.
$template = $this->routeProvider()->getRouteByName($link_templates[$rel])->getPath(); $uri['route_name'] = $link_templates[$rel];
$uri['route_parameters'] = $this->urlRouteParameters($rel);
}
else {
$bundle = $this->bundle();
// A bundle-specific callback takes precedence over the generic one for
// the entity type.
$bundles = \Drupal::entityManager()->getBundleInfo($this->getEntityTypeId());
if (isset($bundles[$bundle]['uri_callback'])) {
$uri_callback = $bundles[$bundle]['uri_callback'];
} }
catch (RouteNotFoundException $e) { elseif ($entity_uri_callback = $this->getEntityType()->getUriCallback()) {
// Fall back to a non-template-based URI. $uri_callback = $entity_uri_callback;
} }
}
if ($template) {
// If there is a template for the given relationship type, do the
// placeholder replacement and use that as the path.
$replacements = $this->uriPlaceholderReplacements();
$uri['path'] = str_replace(array_keys($replacements), array_values($replacements), $template);
// @todo Remove this once http://drupal.org/node/1888424 is in and we can
// move the BC handling of / vs. no-/ to the generator.
$uri['path'] = trim($uri['path'], '/');
// Pass the entity data to url() so that alter functions do not need to
// look up this entity again.
$uri['options']['entity_type'] = $this->entityTypeId;
$uri['options']['entity'] = $this;
return $uri;
}
$bundle = $this->bundle();
// A bundle-specific callback takes precedence over the generic one for
// the entity type.
$bundles = entity_get_bundles($this->entityTypeId);
if (isset($bundles[$bundle]['uri_callback'])) {
$uri_callback = $bundles[$bundle]['uri_callback'];
}
elseif ($entity_uri_callback = $entity_info->getUriCallback()) {
$uri_callback = $entity_uri_callback;
}
// Invoke the callback to get the URI. If there is no callback, use the // Invoke the callback to get the URI. If there is no callback, use the
// default URI format. // default URI format.
// @todo Convert to is_callable() and call_user_func(). // @todo Convert to is_callable() and call_user_func().
if (isset($uri_callback) && function_exists($uri_callback)) { if (isset($uri_callback) && function_exists($uri_callback)) {
$uri = $uri_callback($this); $uri = $uri_callback($this);
} }
// Only use these defaults for a canonical link (that is, a link to self). else {
// Other relationship types are not supported by this logic. return array();
elseif ($rel == 'canonical') { }
$uri = array(
'path' => 'entity/' . $this->entityTypeId . '/' . $this->id(),
);
}
else {
return array();
} }
// Pass the entity data to url() so that alter functions do not need to // Pass the entity data to url() so that alter functions do not need to
// look up this entity again. // look up this entity again.
$uri['options']['entity_type'] = $this->getEntityTypeId();
$uri['options']['entity'] = $this; $uri['options']['entity'] = $this;
return $uri; return $uri;
} }
/**
* {@inheritdoc}
*/
public function getSystemPath($rel = 'canonical') {
if ($uri = $this->urlInfo($rel)) {
return $this->urlGenerator()->getPathFromRoute($uri['route_name'], $uri['route_parameters']);
}
return '';
}
/**
* {@inheritdoc}
*/
public function hasLinkTemplate($rel) {
$link_templates = $this->linkTemplates();
return isset($link_templates[$rel]);
}
/** /**
* Returns an array link templates. * Returns an array link templates.
* *
...@@ -230,6 +193,20 @@ protected function linkTemplates() { ...@@ -230,6 +193,20 @@ protected function linkTemplates() {
return $this->getEntityType()->getLinkTemplates(); return $this->getEntityType()->getLinkTemplates();
} }
/**
* {@inheritdoc}
*/
public function url($rel = 'canonical', $options = array()) {
// While self::urlInfo() will throw an exception if the entity is new,
// the expected result for a URL is always a string.
if ($this->isNew() || !$uri = $this->urlInfo($rel)) {
return '';
}
$options += $uri['options'];
return $this->urlGenerator()->generateFromRoute($uri['route_name'], $uri['route_parameters'], $options);
}
/** /**
* Returns an array of placeholders for this entity. * Returns an array of placeholders for this entity.
* *
...@@ -237,20 +214,22 @@ protected function linkTemplates() { ...@@ -237,20 +214,22 @@ protected function linkTemplates() {
* placeholders if desired. If so, they should be sure to replicate the * placeholders if desired. If so, they should be sure to replicate the
* property caching logic. * property caching logic.
* *
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
*
* @return array * @return array
* An array of URI placeholders. * An array of URI placeholders.
*/ */
protected function uriPlaceholderReplacements() { protected function urlRouteParameters($rel) {
if (empty($this->uriPlaceholderReplacements)) { // The entity ID is needed as a route parameter.
$this->uriPlaceholderReplacements = array( $uri_route_parameters[$this->getEntityTypeId()] = $this->id();
'{entityType}' => $this->getEntityTypeId(),
'{bundle}' => $this->bundle(), // The 'admin-form' link requires the bundle as a route parameter if the
'{id}' => $this->id(), // entity type uses bundles.
'{uuid}' => $this->uuid(), if ($rel == 'admin-form' && $this->getEntityType()->getBundleEntityType() != 'bundle') {
'{' . $this->getEntityTypeId() . '}' => $this->id(), $uri_route_parameters[$this->getEntityType()->getBundleEntityType()] = $this->bundle();
);
} }
return $this->uriPlaceholderReplacements; return $uri_route_parameters;
} }
/** /**
...@@ -403,16 +382,16 @@ protected function onSaveOrDelete() { ...@@ -403,16 +382,16 @@ protected function onSaveOrDelete() {
} }
/** /**
* Wraps the route provider service. * Wraps the URL generator.
* *
* @return \Drupal\Core\Routing\RouteProviderInterface * @return \Drupal\Core\Routing\UrlGeneratorInterface
* The route provider. * The URL generator.
*/ */
protected function routeProvider() { protected function urlGenerator() {
if (!$this->routeProvider) { if (!$this->urlGenerator) {
$this->routeProvider = \Drupal::service('router.route_provider'); $this->urlGenerator = \Drupal::urlGenerator();
} }
return $this->routeProvider; return $this->urlGenerator;
} }
} }
...@@ -309,7 +309,15 @@ public function save(array $form, array &$form_state) { ...@@ -309,7 +309,15 @@ public function save(array $form, array &$form_state) {
* A reference to a keyed array containing the current state of the form. * A reference to a keyed array containing the current state of the form.
*/ */
public function delete(array $form, array &$form_state) { public function delete(array $form, array &$form_state) {
// @todo Perform common delete operations. if ($this->entity->hasLinkTemplate('delete-form')) {
$form_state['redirect_route'] = $this->entity->urlInfo('delete-form');
$query = $this->getRequest()->query;
if ($query->has('destination')) {
$form_state['redirect_route']['options']['query']['destination'] = $query->get('destination');
$query->remove('destination');
}
}
} }
/** /**
......
...@@ -97,11 +97,71 @@ public function label(); ...@@ -97,11 +97,71 @@ public function label();
/** /**
* Returns the URI elements of the entity. * Returns the URI elements of the entity.
* *
* URI templates might be set in the links array in an annotation, for
* example:
* @code
* links = {
* "canonical" = "/node/{node}",
* "edit-form" = "/node/{node}/edit",
* "version-history" = "/node/{node}/revisions"
* }
* @endcode
* or specified in a callback function set like:
* @code
* uri_callback = "contact_category_uri",
* @endcode
* If the path is not set in the links array, the uri_callback function is
* used for setting the path. If this does not exist and the link relationship
* type is canonical, the path is set using the default template:
* entity/entityType/id.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
*
* @return * @return
* An array containing the 'path' and 'options' keys used to build the URI * An array containing the 'path' and 'options' keys used to build the URI
* of the entity, and matching the signature of url(). * of the entity, and matching the signature of url().
*/ */
public function uri(); public function urlInfo($rel = 'canonical');
/**
* Returns the public URL for this entity.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
* @param array $options
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
* the available options.
*
* @return string
* The URL for this entity.
*/
public function url($rel = 'canonical', $options = array());
/**
* Returns the internal path for this entity.
*
* self::url() will return the full path including any prefixes, fragments, or
* query strings. This path does not include those.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
*
* @return string
* The internal path for this entity.
*/
public function getSystemPath($rel = 'canonical');
/**
* Indicates if a link template exists for a given key.
*
* @param string $key
* The link type.
*
* @return bool
* TRUE if the link template exists, FALSE otherwise.
*/
public function hasLinkTemplate($key);
/** /**
* Returns a list of URI relationships supported by this entity. * Returns a list of URI relationships supported by this entity.
......
...@@ -93,24 +93,18 @@ protected function getLabel(EntityInterface $entity) { ...@@ -93,24 +93,18 @@ protected function getLabel(EntityInterface $entity) {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getOperations(EntityInterface $entity) { public function getOperations(EntityInterface $entity) {
$uri = $entity->uri();
$operations = array(); $operations = array();
if ($entity->access('update')) { if ($entity->access('update') && $entity->hasLinkTemplate('edit-form')) {
$operations['edit'] = array( $operations['edit'] = array(
'title' => $this->t('Edit'), 'title' => $this->t('Edit'),
'href' => $uri['path'] . '/edit',
'options' => $uri['options'],
'weight' => 10, 'weight' => 10,
); ) + $entity->urlInfo('edit-form');
} }
if ($entity->access('delete')) { if ($entity->access('delete') && $entity->hasLinkTemplate('delete-form')) {
$operations['delete'] = array( $operations['delete'] = array(
'title' => $this->t('Delete'), 'title' => $this->t('Delete'),
'href' => $uri['path'] . '/delete',
'options' => $uri['options'],
'weight' => 100, 'weight' => 100,
); ) + $entity->urlInfo('delete-form');
} }
return $operations; return $operations;
......
...@@ -26,7 +26,7 @@ public function getStorageController(); ...@@ -26,7 +26,7 @@ public function getStorageController();
* This allows the controller to manipulate the list, like filtering or * This allows the controller to manipulate the list, like filtering or
* sorting the loaded entities. * sorting the loaded entities.
* *
* @return array * @return \Drupal\Core\Entity\EntityInterface[]
* An array of entities implementing Drupal\Core\Entity\EntityInterface. * An array of entities implementing Drupal\Core\Entity\EntityInterface.
*/ */
public function load(); public function load();
......
...@@ -391,17 +391,14 @@ public function isFieldable(); ...@@ -391,17 +391,14 @@ public function isFieldable();
* HTML page must also define an "edit-form" relationship. * HTML page must also define an "edit-form" relationship.
* *
* By default, the following placeholders are supported: * By default, the following placeholders are supported:
* - entityType: The machine name of the entity type.
* - bundle: The bundle machine name of the entity.
* - id: The unique ID of the entity.
* - uuid: The UUID of the entity.
* - [entityType]: The entity type itself will also be a valid token for the * - [entityType]: The entity type itself will also be a valid token for the
* ID of the entity. For instance, a placeholder of {node} used on the Node * ID of the entity. For instance, a placeholder of {node} used on the Node
* class would have the same value as {id}. This is generally preferred * class.
* over "id" for better self-documentation. * - [bundleEntityType]: The bundle machine name itself. For instance, a
* placeholder of {node_type} used on the Node class.
* *
* Specific entity types may also expand upon this list by overriding the * Specific entity types may also expand upon this list by overriding the
* Entity::uriPlaceholderReplacements() method.