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) {
/**
* {@inheritdoc}
*/
public function uri() {
return parent::uri('edit-form');
public function urlInfo($rel = '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() {
*/
public function getOperations(EntityInterface $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 (!$entity->status()) {
if (!$entity->status() && $entity->hasLinkTemplate('enable')) {
$operations['enable'] = array(
'title' => t('Enable'),
'href' => $uri['path'] . '/enable',
'options' => $uri['options'],
'weight' => -10,
);
) + $entity->urlInfo('enable');
}
else {
elseif ($entity->hasLinkTemplate('disable')) {
$operations['disable'] = array(
'title' => t('Disable'),
'href' => $uri['path'] . '/disable',
'options' => $uri['options'],
'weight' => 40,
);
) + $entity->urlInfo('disable');
}
}
......
......@@ -9,7 +9,6 @@
use Drupal\Core\Language\Language;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* Defines a base entity class.
......@@ -38,18 +37,11 @@ abstract class Entity implements EntityInterface {
protected $enforceIsNew;
/**
* The route provider service.
* The URL generator.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $routeProvider;
/**
* Local cache for URI placeholder substitution values.
*
* @var array
*/
protected $uriPlaceholderReplacements;
protected $urlGenerator;
/**
* Constructs an Entity object.
......@@ -127,99 +119,70 @@ public function label() {
}
/**
* 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 array
* An array containing the 'path' and 'options' keys used to build the URI
* of the entity, and matching the signature of url().
* {@inheritdoc}
*/
public function uri($rel = 'canonical') {
$entity_info = $this->getEntityType();
public function urlInfo($rel = 'canonical') {
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.
$link_templates = $this->linkTemplates();
$template = NULL;
if (isset($link_templates[$rel])) {
try {
$template = $this->routeProvider()->getRouteByName($link_templates[$rel])->getPath();
// If there is a template for the given relationship type, generate the path.
$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) {
// Fall back to a non-template-based URI.
elseif ($entity_uri_callback = $this->getEntityType()->getUriCallback()) {
$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
// default URI format.
// @todo Convert to is_callable() and call_user_func().
if (isset($uri_callback) && function_exists($uri_callback)) {
$uri = $uri_callback($this);
}
// Only use these defaults for a canonical link (that is, a link to self).
// Other relationship types are not supported by this logic.
elseif ($rel == 'canonical') {
$uri = array(
'path' => 'entity/' . $this->entityTypeId . '/' . $this->id(),
);
}
else {
return array();
// Invoke the callback to get the URI. If there is no callback, use the
// default URI format.
// @todo Convert to is_callable() and call_user_func().
if (isset($uri_callback) && function_exists($uri_callback)) {
$uri = $uri_callback($this);
}
else {
return array();
}
}
// Pass the entity data to url() so that alter functions do not need to
// look up this entity again.
$uri['options']['entity_type'] = $this->getEntityTypeId();
$uri['options']['entity'] = $this;
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.
*
......@@ -230,6 +193,20 @@ protected function linkTemplates() {
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.
*
......@@ -237,20 +214,22 @@ protected function linkTemplates() {
* placeholders if desired. If so, they should be sure to replicate the
* property caching logic.
*
* @param string $rel
* The link relationship type, for example: canonical or edit-form.
*
* @return array
* An array of URI placeholders.
*/
protected function uriPlaceholderReplacements() {
if (empty($this->uriPlaceholderReplacements)) {
$this->uriPlaceholderReplacements = array(
'{entityType}' => $this->getEntityTypeId(),
'{bundle}' => $this->bundle(),
'{id}' => $this->id(),
'{uuid}' => $this->uuid(),
'{' . $this->getEntityTypeId() . '}' => $this->id(),
);
protected function urlRouteParameters($rel) {
// The entity ID is needed as a route parameter.
$uri_route_parameters[$this->getEntityTypeId()] = $this->id();
// The 'admin-form' link requires the bundle as a route parameter if the
// entity type uses bundles.
if ($rel == 'admin-form' && $this->getEntityType()->getBundleEntityType() != 'bundle') {
$uri_route_parameters[$this->getEntityType()->getBundleEntityType()] = $this->bundle();
}
return $this->uriPlaceholderReplacements;
return $uri_route_parameters;
}
/**
......@@ -403,16 +382,16 @@ protected function onSaveOrDelete() {
}
/**
* Wraps the route provider service.
* Wraps the URL generator.
*
* @return \Drupal\Core\Routing\RouteProviderInterface
* The route provider.
* @return \Drupal\Core\Routing\UrlGeneratorInterface
* The URL generator.
*/
protected function routeProvider() {
if (!$this->routeProvider) {
$this->routeProvider = \Drupal::service('router.route_provider');
protected function urlGenerator() {
if (!$this->urlGenerator) {
$this->urlGenerator = \Drupal::urlGenerator();
}
return $this->routeProvider;
return $this->urlGenerator;
}
}
......@@ -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.
*/
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();
/**
* 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
* 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();
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.
......
......@@ -93,24 +93,18 @@ protected function getLabel(EntityInterface $entity) {
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$uri = $entity->uri();
$operations = array();
if ($entity->access('update')) {
if ($entity->access('update') && $entity->hasLinkTemplate('edit-form')) {
$operations['edit'] = array(
'title' => $this->t('Edit'),
'href' => $uri['path'] . '/edit',
'options' => $uri['options'],
'weight' => 10,
);
) + $entity->urlInfo('edit-form');
}
if ($entity->access('delete')) {
if ($entity->access('delete') && $entity->hasLinkTemplate('delete-form')) {
$operations['delete'] = array(
'title' => $this->t('Delete'),
'href' => $uri['path'] . '/delete',
'options' => $uri['options'],
'weight' => 100,
);
) + $entity->urlInfo('delete-form');
}
return $operations;
......
......@@ -26,7 +26,7 @@ public function getStorageController();
* This allows the controller to manipulate the list, like filtering or
* sorting the loaded entities.
*
* @return array
* @return \Drupal\Core\Entity\EntityInterface[]
* An array of entities implementing Drupal\Core\Entity\EntityInterface.
*/
public function load();
......
......@@ -391,17 +391,14 @@ public function isFieldable();
* HTML page must also define an "edit-form" relationship.
*
* 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
* 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
* over "id" for better self-documentation.
* class.
* - [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
* Entity::uriPlaceholderReplacements() method.
* Entity::urlRouteParameters() method.
*
* @link http://www.iana.org/assignments/link-relations/link-relations.xml @endlink
* @link http://tools.ietf.org/html/rfc6570 @endlink
......
......@@ -80,5 +80,6 @@ function action_entity_info(&$entity_info) {
->setFormClass('edit', 'Drupal\action\ActionEditFormController')
->setFormClass('delete', 'Drupal\action\Form\ActionDeleteForm')
->setListClass('Drupal\action\ActionListController')
->setLinkTemplate('delete-form', 'action.delete')
->setLinkTemplate('edit-form', 'action.admin_configure');
}
......@@ -28,6 +28,12 @@
* "remove_items" = "Drupal\aggregator\Form\FeedItemsRemoveForm",
* }
* },
* links = {
* "canonical" = "aggregator.feed_view",
* "edit-form" = "aggregator.feed_configure",
* "delete-form" = "aggregator.feed_delete",
* "edit-form" = "aggregator.feed_configure",
* },
* base_table = "aggregator_feed",
* fieldable = TRUE,
* entity_keys = {
......
......@@ -92,10 +92,7 @@ public function save(array $form, array &$form_state) {
$form_state['redirect_route']['route_name'] = 'aggregator.admin_overview';
}
else {
$form_state['redirect_route'] = array(
'route_name' => 'aggregator.feed_view',
'route_parameters' => array('aggregator_feed' => $feed->id()),
);
$form_state['redirect_route'] = $feed->urlInfo('canonical');
}
}
else {
......@@ -104,14 +101,4 @@ public function save(array $form, array &$form_state) {
}
}
/**
* {@inheritdoc}
*/
public function delete(array $form, array &$form_state) {
$form_state['redirect_route'] = array(
'route_name' => 'aggregator.feed_delete',
'route_parameters' => array('aggregator_feed' => $this->entity->id()),
);
}
}
......@@ -248,27 +248,6 @@ public function save(array $form, array &$form_state) {
Cache::invalidateTags(array('content' => TRUE));
}
/**
* {@inheritdoc}
*/
public function delete(array $form, array &$form_state) {
$destination = array();
$query = $this->getRequest()->query;
if (!is_null($query->get('destination'))) {
$destination = drupal_get_destination();
$query->remove('destination');
}
$form_state['redirect_route'] = array(
'route_name' => 'custom_block.delete',
'route_parameters' => array(
'custom_block' => $this->entity->id(),
),
'options' => array(
'query' => $destination,
),
);
}
/**
* {@inheritdoc}
*/
......
......@@ -36,10 +36,7 @@ public function buildRow(EntityInterface $entity) {
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
// The custom block edit path does not contain '/edit'.
if (isset($operations['edit'])) {
$uri = $entity->uri();
$operations['edit']['href'] = $uri['path'];
$operations['edit']['query']['destination'] = 'admin/structure/block/custom-blocks';
}
return $operations;
......
......@@ -91,29 +91,18 @@ public function save(array $form, array &$form_state) {
$block_type = $this->entity;
$status = $block_type->save();
$uri = $block_type->uri();
$uri = $block_type->urlInfo();
$edit_link = \Drupal::l($this->t('Edit'), $uri['route_name'], $uri['route_parameters'], $uri['options']);
if ($status == SAVED_UPDATED) {
drupal_set_message(t('Custom block type %label has been updated.', array('%label' => $block_type->label())));
watchdog('custom_block', 'Custom block type %label has been updated.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
watchdog('custom_block', 'Custom block type %label has been updated.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, $edit_link);
}
else {
drupal_set_message(t('Custom block type %label has been added.', array('%label' => $block_type->label())));
watchdog('custom_block', 'Custom block type %label has been added.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
watchdog('custom_block', 'Custom block type %label has been added.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, $edit_link);
}
$form_state['redirect_route']['route_name'] = 'custom_block.type_list';
}
/**
* Overrides \Drupal\Core\Entity\EntityFormController::delete().
*/
public function delete(array $form, array &$form_state) {
$form_state['redirect_route'] = array(
'route_name' => 'custom_block.type_delete',
'route_parameters' => array(
'custom_block_type' => $this->entity->id(),
),
);
}
}
......@@ -41,8 +41,8 @@ public function buildHeader() {
* Overrides \Drupal\Core\Entity\EntityListController::buildRow().
*/
public function buildRow(EntityInterface $entity) {
$uri = $entity->uri();
$row['type'] = l($entity->label(), $uri['path'], $uri['options']);
$uri = $entity->urlInfo();
$row['type'] = \Drupal::l($entity->label(), $uri['route_name'], $uri['route_parameters'], $uri['options']);
$row['description'] = filter_xss_admin($entity->description);
return $row + parent::buildRow($entity);
}
......
......@@ -37,6 +37,7 @@
* revision_table = "custom_block_revision",
* links = {
* "canonical" = "custom_block.edit",
* "delete-form" = "custom_block.delete",
* "edit-form" = "custom_block.edit",
* "admin-form" = "custom_block.type_edit"
* },
......
......@@ -36,6 +36,7 @@
* "uuid" = "uuid"
* },
* links = {
* "delete-form" = "custom_block.type_delete",
* "edit-form" = "custom_block.type_edit"
* }
* )
......
......@@ -340,24 +340,6 @@ public function submit(array $form, array &$form_state) {
);
}