Commit f2fa574c authored by alexpott's avatar alexpott

Issue #2235457 by dawehner, amateescu, hussainweb, Berdir, benjy, Wim Leers,...

Issue #2235457 by dawehner, amateescu, hussainweb, Berdir, benjy, Wim Leers, lokapujya, RavindraSingh, Ryan Weal, jibran, Jalandhar: Use link field for shortcut entity
parent f2bee5d5
...@@ -118,4 +118,12 @@ public function save(array $form, FormStateInterface $form_state) {} ...@@ -118,4 +118,12 @@ public function save(array $form, FormStateInterface $form_state) {}
*/ */
public function delete(array $form, FormStateInterface $form_state) {} public function delete(array $form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
// Override the default validation implementation as it is not necessary
// nor possible to validate an entity in a confirmation form.
}
} }
...@@ -37,4 +37,11 @@ interface LinkItemInterface extends FieldItemInterface { ...@@ -37,4 +37,11 @@ interface LinkItemInterface extends FieldItemInterface {
*/ */
public function isExternal(); public function isExternal();
/**
* Gets the URL object.
*
* @return \Drupal\Core\Url
*/
public function getUrl();
} }
...@@ -164,4 +164,29 @@ public function isExternal() { ...@@ -164,4 +164,29 @@ public function isExternal() {
public static function mainPropertyName() { public static function mainPropertyName() {
return 'uri'; return 'uri';
} }
/**
* Gets the URL object.
*
* @return \Drupal\Core\Url
*/
public function getUrl() {
return \Drupal::pathValidator()->getUrlIfValidWithoutAccessCheck($this->uri);
}
/**
* {@inheritdoc}
*/
public function setValue($values, $notify = TRUE) {
// Unserialize the values.
// @todo The storage controller should take care of this, see
// SqlContentEntityStorage::loadFieldItems, see
// https://www.drupal.org/node/2414835
if (isset($values['options']) && is_string($values['options'])) {
$values['options'] = unserialize($values['options']);
}
parent::setValue($values, $notify);
}
} }
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\link\Plugin\Field\FieldWidget; namespace Drupal\link\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase; use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
...@@ -43,8 +44,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen ...@@ -43,8 +44,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$default_url_value = NULL; $default_url_value = NULL;
if (isset($items[$delta]->uri)) { if (isset($items[$delta]->uri)) {
if ($url = \Drupal::pathValidator()->getUrlIfValid($items[$delta]->uri)) { if ($url = \Drupal::pathValidator()->getUrlIfValid($items[$delta]->uri)) {
$url->setOptions($items[$delta]->options); $url->setOptions($items[$delta]->options ?: []);
$default_url_value = ltrim($url->toString(), '/'); $url_string = $url->toString();
$default_url_value = $url->isRouted() ? Unicode::substr($url_string, strlen(base_path())) : $url_string;
} }
} }
$element['uri'] = array( $element['uri'] = array(
......
...@@ -5,3 +5,5 @@ package: Core ...@@ -5,3 +5,5 @@ package: Core
version: VERSION version: VERSION
core: 8.x core: 8.x
configure: entity.shortcut_set.collection configure: entity.shortcut_set.collection
dependencies:
- link
...@@ -5,9 +5,6 @@ ...@@ -5,9 +5,6 @@
* Install, update and uninstall functions for the shortcut module. * Install, update and uninstall functions for the shortcut module.
*/ */
use Drupal\Core\Database\Database;
use Drupal\Core\Language\Language;
/** /**
* Implements hook_schema(). * Implements hook_schema().
*/ */
......
...@@ -256,10 +256,10 @@ function shortcut_renderable_links($shortcut_set = NULL) { ...@@ -256,10 +256,10 @@ function shortcut_renderable_links($shortcut_set = NULL) {
$cache_tags = array(); $cache_tags = array();
foreach ($shortcuts as $shortcut) { foreach ($shortcuts as $shortcut) {
$shortcut = \Drupal::entityManager()->getTranslationFromContext($shortcut); $shortcut = \Drupal::entityManager()->getTranslationFromContext($shortcut);
$links[] = array( $links[$shortcut->id()] = array(
'type' => 'link',
'title' => $shortcut->label(), 'title' => $shortcut->label(),
'url' => Url::fromRoute($shortcut->getRouteName(), $shortcut->getRouteParameters()), ) + $shortcut->getUrl()->toArray();
);
$cache_tags = Cache::mergeTags($cache_tags, $shortcut->getCacheTags()); $cache_tags = Cache::mergeTags($cache_tags, $shortcut->getCacheTags());
} }
...@@ -310,8 +310,9 @@ function shortcut_preprocess_page(&$variables) { ...@@ -310,8 +310,9 @@ function shortcut_preprocess_page(&$variables) {
// Check if $link is already a shortcut and set $link_mode accordingly. // Check if $link is already a shortcut and set $link_mode accordingly.
$shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(array('shortcut_set' => $shortcut_set->id())); $shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(array('shortcut_set' => $shortcut_set->id()));
/** @var \Drupal\shortcut\ShortcutInterface $shortcut */
foreach ($shortcuts as $shortcut) { foreach ($shortcuts as $shortcut) {
if ($shortcut->getRouteName() == $route_match->getRouteName() && $shortcut->getRouteParameters() == $route_match->getParameters()->all()) { if (($shortcut_url = $shortcut->getUrl()) && $shortcut_url->isRouted() && $shortcut_url->getRouteName() == $route_match->getRouteName() && $shortcut_url->getRouteParameters() == $route_match->getParameters()->all()) {
$shortcut_id = $shortcut->id(); $shortcut_id = $shortcut->id();
break; break;
} }
......
...@@ -64,7 +64,9 @@ public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Reques ...@@ -64,7 +64,9 @@ public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Reques
$shortcut = $this->entityManager()->getStorage('shortcut')->create(array( $shortcut = $this->entityManager()->getStorage('shortcut')->create(array(
'title' => $name, 'title' => $name,
'shortcut_set' => $shortcut_set->id(), 'shortcut_set' => $shortcut_set->id(),
'path' => $link, 'link' => array(
'uri' => $link,
),
)); ));
try { try {
......
...@@ -12,13 +12,14 @@ ...@@ -12,13 +12,14 @@
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Url; use Drupal\link\LinkItemInterface;
use Drupal\shortcut\ShortcutInterface; use Drupal\shortcut\ShortcutInterface;
use Symfony\Component\HttpFoundation\Request;
/** /**
* Defines the shortcut entity class. * Defines the shortcut entity class.
* *
* @property \Drupal\link\LinkItemInterface link
*
* @ContentEntityType( * @ContentEntityType(
* id = "shortcut", * id = "shortcut",
* label = @Translation("Shortcut link"), * label = @Translation("Shortcut link"),
...@@ -65,7 +66,7 @@ public function getTitle() { ...@@ -65,7 +66,7 @@ public function getTitle() {
*/ */
public function setTitle($link_title) { public function setTitle($link_title) {
$this->set('title', $link_title); $this->set('title', $link_title);
return $this; return $this;
} }
/** /**
...@@ -87,67 +88,7 @@ public function setWeight($weight) { ...@@ -87,67 +88,7 @@ public function setWeight($weight) {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getUrl() { public function getUrl() {
return new Url($this->getRouteName(), $this->getRouteParameters()); return $this->link->first()->getUrl();
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
return $this->get('route_name')->value;
}
/**
* {@inheritdoc}
*/
public function setRouteName($route_name) {
$this->set('route_name', $route_name);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRouteParameters() {
return $this->get('route_parameters')->first()->getValue();
}
/**
* {@inheritdoc}
*/
public function setRouteParameters($route_parameters) {
$this->set('route_parameters', $route_parameters);
return $this;
}
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage, array &$values) {
parent::preCreate($storage, $values);
if (!isset($values['shortcut_set'])) {
$values['shortcut_set'] = 'default';
}
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// @todo fix PathValidatorInterface::getUrlIfValid() so we can use it
// here. The problem is that we need an exception, not a FALSE
// return value. https://www.drupal.org/node/2346695
if ($this->path->value == '<front>') {
$url = new Url($this->path->value);
}
else {
$url = Url::createFromRequest(Request::create("/{$this->path->value}"));
}
$this->setRouteName($url->getRouteName());
$this->setRouteParameters($url->getRouteParameters());
} }
/** /**
...@@ -205,13 +146,22 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -205,13 +146,22 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setLabel(t('Weight')) ->setLabel(t('Weight'))
->setDescription(t('Weight among shortcuts in the same shortcut set.')); ->setDescription(t('Weight among shortcuts in the same shortcut set.'));
$fields['route_name'] = BaseFieldDefinition::create('string') $fields['link'] = BaseFieldDefinition::create('link')
->setLabel(t('Route name')) ->setLabel(t('Path'))
->setDescription(t('The machine name of a defined Route this shortcut represents.')); ->setDescription(t('The location this shortcut points to.'))
->setRequired(TRUE)
$fields['route_parameters'] = BaseFieldDefinition::create('map') ->setTranslatable(FALSE)
->setLabel(t('Route parameters')) ->setSettings(array(
->setDescription(t('A serialized array of route parameters of this shortcut.')); 'default_value' => '',
'max_length' => 560,
'link_type' => LinkItemInterface::LINK_INTERNAL,
'title' => DRUPAL_DISABLED,
))
->setDisplayOptions('form', array(
'type' => 'link_default',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
$fields['langcode'] = BaseFieldDefinition::create('language') $fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language')) ->setLabel(t('Language'))
...@@ -224,16 +174,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -224,16 +174,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
'weight' => 2, 'weight' => 2,
)); ));
$fields['path'] = BaseFieldDefinition::create('string')
->setLabel(t('Path'))
->setDescription(t('The computed shortcut path.'))
->setComputed(TRUE)
->setCustomStorage(TRUE);
$item_definition = $fields['path']->getItemDefinition();
$item_definition->setClass('\Drupal\shortcut\ShortcutPathItem');
$fields['path']->setItemDefinition($item_definition);
return $fields; return $fields;
} }
......
...@@ -52,7 +52,11 @@ public function form(array $form, FormStateInterface $form_state) { ...@@ -52,7 +52,11 @@ public function form(array $form, FormStateInterface $form_state) {
foreach ($this->entity->getShortcuts() as $shortcut) { foreach ($this->entity->getShortcuts() as $shortcut) {
$id = $shortcut->id(); $id = $shortcut->id();
$form['shortcuts']['links'][$id]['#attributes']['class'][] = 'draggable'; $form['shortcuts']['links'][$id]['#attributes']['class'][] = 'draggable';
$form['shortcuts']['links'][$id]['name']['#markup'] = $this->l($shortcut->getTitle(), $shortcut->getUrl()); $form['shortcuts']['links'][$id]['name'] = array(
'#type' => 'link',
'#title' => $shortcut->getTitle(),
) + $shortcut->getUrl()->toRenderArray();
unset($form['shortcuts']['links'][$id]['name']['#access_callback']);
$form['shortcuts']['links'][$id]['#weight'] = $shortcut->getWeight(); $form['shortcuts']['links'][$id]['#weight'] = $shortcut->getWeight();
$form['shortcuts']['links'][$id]['weight'] = array( $form['shortcuts']['links'][$id]['weight'] = array(
'#type' => 'weight', '#type' => 'weight',
......
...@@ -27,7 +27,7 @@ public function getFormId() { ...@@ -27,7 +27,7 @@ public function getFormId() {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getQuestion() { public function getQuestion() {
return $this->t('Are you sure you want to delete the shortcut %title?', array('%title' => $this->entity->title->value)); return $this->t('Are you sure you want to delete the shortcut %title?', array('%title' => $this->entity->getTitle()));
} }
/** /**
......
...@@ -8,10 +8,7 @@ ...@@ -8,10 +8,7 @@
namespace Drupal\shortcut; namespace Drupal\shortcut;
use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/** /**
* Form controller for the shortcut entity forms. * Form controller for the shortcut entity forms.
...@@ -25,76 +22,6 @@ class ShortcutForm extends ContentEntityForm { ...@@ -25,76 +22,6 @@ class ShortcutForm extends ContentEntityForm {
*/ */
protected $entity; protected $entity;
/**
* The path validator.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* Constructs a new ShortcutForm instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager
* @param \Drupal\Core\Path\PathValidatorInterface $path_validator
* The path validator.
*/
public function __construct(EntityManagerInterface $entity_manager, PathValidatorInterface $path_validator) {
parent::__construct($entity_manager);
$this->pathValidator = $path_validator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager'), $container->get('path.validator'));
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form['path'] = array(
'#type' => 'textfield',
'#title' => t('Path'),
'#size' => 40,
'#maxlength' => 255,
'#field_prefix' => $this->url('<front>', array(), array('absolute' => TRUE)),
'#default_value' => $this->entity->path->value,
);
return $form;
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
$entity = parent::buildEntity($form, $form_state);
// Set the computed 'path' value so it can used in the preSave() method to
// derive the route name and parameters.
$entity->path->value = $form_state->getValue('path');
return $entity;
}
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
if (!$this->pathValidator->isValid($form_state->getValue('path'))) {
$form_state->setErrorByName('path', $this->t('The shortcut must correspond to a valid path on the site.'));
}
parent::validate($form, $form_state);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
......
...@@ -60,42 +60,4 @@ public function setWeight($weight); ...@@ -60,42 +60,4 @@ public function setWeight($weight);
*/ */
public function getUrl(); public function getUrl();
/**
* Returns the route name associated with this shortcut, if any.
*
* @return string|null
* The route name of this shortcut.
*/
public function getRouteName();
/**
* Sets the route name associated with this shortcut.
*
* @param string|null $route_name
* The route name associated with this shortcut.
*
* @return \Drupal\shortcut\ShortcutInterface
* The called shortcut entity.
*/
public function setRouteName($route_name);
/**
* Returns the route parameters associated with this shortcut, if any.
*
* @return array
* The route parameters of this shortcut.
*/
public function getRouteParameters();
/**
* Sets the route parameters associated with this shortcut.
*
* @param array $route_parameters
* The route parameters associated with this shortcut.
*
* @return \Drupal\shortcut\ShortcutInterface
* The called shortcut entity.
*/
public function setRouteParameters($route_parameters);
} }
<?php
/**
* @file
* Contains \Drupal\shortcut\ShortcutPathItem.
*/
namespace Drupal\shortcut;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\TypedData\DataDefinition;
/**
* The field item for the 'path' field.
*/
class ShortcutPathItem extends StringItem {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('string')
->setLabel(t('String value'))
->setComputed(TRUE)
->setClass('\Drupal\shortcut\ShortcutPathValue')
->setRequired(TRUE);
return $properties;
}
}
<?php
/**
* @file
* Contains \Drupal\shortcut\ShortcutPathValue.
*/
namespace Drupal\shortcut;
use Drupal\Core\TypedData\TypedData;
/**
* A computed property for the string value of the path field.
*/
class ShortcutPathValue extends TypedData {
/**
* {@inheritdoc}
*/
public function getValue() {
if (!isset($this->value)) {
if (!isset($this->parent)) {
throw new \InvalidArgumentException('Computed properties require context for computation.');
}
$entity = $this->parent->getEntity();
if ($route_name = $entity->getRouteName()) {
$path = \Drupal::urlGenerator()->getPathFromRoute($route_name, $entity->getRouteParameters());
$this->value = trim($path, '/');
}
else {
$this->value = NULL;
}
}
return $this->value;
}
/**
* {@inheritdoc}
*/
public function setValue($value, $notify = TRUE) {
// Normalize the path in case it is an alias.
$value = \Drupal::service('path.alias_manager')->getPathByAlias($value);
if (empty($value)) {
$value = '<front>';
}
parent::setValue($value, $notify);
}
}
...@@ -43,10 +43,10 @@ protected function setUp() { ...@@ -43,10 +43,10 @@ protected function setUp() {
protected function createEntity() { protected function createEntity() {
// Create a "Llama" shortcut. // Create a "Llama" shortcut.
$shortcut = Shortcut::create(array( $shortcut = Shortcut::create(array(
'set' => 'default', 'shortcut_set' => 'default',
'title' => t('Llama'), 'title' => t('Llama'),
'weight' => 0, 'weight' => 0,
'path' => 'admin', 'link' => ['uri' => 'admin'],
)); ));
$shortcut->save(); $shortcut->save();
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
*/ */
namespace Drupal\shortcut\Tests; namespace Drupal\shortcut\Tests;
use Drupal\Component\Utility\String;
use Drupal\Core\Url;
use Drupal\shortcut\Entity\Shortcut; use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet; use Drupal\shortcut\Entity\ShortcutSet;
...@@ -37,42 +40,40 @@ public function testShortcutLinkAdd() { ...@@ -37,42 +40,40 @@ public function testShortcutLinkAdd() {
$this->container->get('path.alias_storage')->save($path['source'], $path['alias']); $this->container->get('path.alias_storage')->save($path['source'], $path['alias']);
// Create some paths to test. // Create some paths to test.
$test_cases = array( $test_cases = [
array('path' => '', 'route_name' => '<front>'), '<front>',
array('path' => '<front>', 'route_name' => '<front>'), 'admin',
array('path' => 'admin', 'route_name' => 'system.admin'), 'admin/config/system/site-information',
array('path' => 'admin/config/system/site-information', 'route_name' => 'system.site_information_settings'), 'node/' . $this->node->id() . '/edit',
array('path' => 'node/' . $this->node->id() . '/edit', 'route_name' => 'entity.node.edit_form'), $path['alias'],
array('path' => $path['alias'], 'route_name' => 'entity.node.canonical'), 'router_test/test2',
array('path' => 'router_test/test2', 'route_name' => 'router_test.2'), 'router_test/test3/value',
array('path' => 'router_test/test3/value', 'route_name' => 'router_test.3'), ];
);
// Check that each new shortcut links where it should. // Check that each new shortcut links where it should.
foreach ($test_cases as $test) { foreach ($test_cases as $test_path) {
$title = $this->randomMachineName(); $title = $this->randomMachineName();
$form_data = array( $form_data = array(
'title[0][value]' => $title, 'title[0][value]' => $title,
'path' => $test['path'], 'link[0][uri]' => $test_path,
); );
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save')); $this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertResponse(200); $this->assertResponse(200);
$saved_set = ShortcutSet::load($set->id()); $saved_set = ShortcutSet::load($set->id());
$routes = $this->getShortcutInformation($saved_set, 'route_name'); $paths = $this->getShortcutInformation($saved_set, 'link');
$this->assertTrue(in_array($test['route_name'], $routes), 'Shortcut created: ' . $test['path']); $test_path = $test_path != '<front>' ? $test_path : '';
$this->assertLink($title, 0, 'Shortcut link found on the page.'); $this->assertTrue(in_array($test_path, $paths), 'Shortcut created: ' . $test_path);
$this->assertLink($title, 0, String::format('Shortcut link %url found on the page.', ['%url' => $test_path]));
} }
$saved_set = ShortcutSet::load($set->id()); $saved_set = ShortcutSet::load($set->id());
// Test that saving and re-loading a shortcut preserves its values. // Test that saving and re-loading a shortcut preserves its values.
$shortcuts = $saved_set->getShortcuts(); $shortcuts = $saved_set->getShortcuts();
foreach ($shortcuts as $entity) { foreach ($shortcuts as $entity) {
// Test the node routes with parameters. // Test the node routes with parameters.
if (strpos($entity->route_name->value, 'node.') === 0) { $entity->save();
$entity->save(); $loaded = Shortcut::load($entity->id());
$loaded = Shortcut::load($entity->id()); $this->assertEqual($entity->link->uri, $loaded->link->uri);
$this->assertEqual($entity->route_name->value, $loaded->route_name->value); $this->assertEqual($entity->link->options, $loaded->link->options);
$this->assertEqual($entity->get('route_parameters')->first()->getValue(), $loaded->get('route_parameters')->first()->getValue());
}
} }
// Login as non admin user, to check that access is checked when creating // Login as non admin user, to check that access is checked when creating
...@@ -81,15 +82,15 @@ public function testShortcutLinkAdd() { ...@@ -81,15 +82,15 @@ public function testShortcutLinkAdd() {
$title = $this->randomMachineName(); $title = $this->randomMachineName();