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) {}
*/
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 {
*/
public function isExternal();
/**
* Gets the URL object.
*
* @return \Drupal\Core\Url
*/
public function getUrl();
}
......@@ -164,4 +164,29 @@ public function isExternal() {
public static function mainPropertyName() {
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 @@
namespace Drupal\link\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
......@@ -43,8 +44,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$default_url_value = NULL;
if (isset($items[$delta]->uri)) {
if ($url = \Drupal::pathValidator()->getUrlIfValid($items[$delta]->uri)) {
$url->setOptions($items[$delta]->options);
$default_url_value = ltrim($url->toString(), '/');
$url->setOptions($items[$delta]->options ?: []);
$url_string = $url->toString();
$default_url_value = $url->isRouted() ? Unicode::substr($url_string, strlen(base_path())) : $url_string;
}
}
$element['uri'] = array(
......
......@@ -5,3 +5,5 @@ package: Core
version: VERSION
core: 8.x
configure: entity.shortcut_set.collection
dependencies:
- link
......@@ -5,9 +5,6 @@
* Install, update and uninstall functions for the shortcut module.
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Language\Language;
/**
* Implements hook_schema().
*/
......
......@@ -256,10 +256,10 @@ function shortcut_renderable_links($shortcut_set = NULL) {
$cache_tags = array();
foreach ($shortcuts as $shortcut) {
$shortcut = \Drupal::entityManager()->getTranslationFromContext($shortcut);
$links[] = array(
$links[$shortcut->id()] = array(
'type' => 'link',
'title' => $shortcut->label(),
'url' => Url::fromRoute($shortcut->getRouteName(), $shortcut->getRouteParameters()),
);
) + $shortcut->getUrl()->toArray();
$cache_tags = Cache::mergeTags($cache_tags, $shortcut->getCacheTags());
}
......@@ -310,8 +310,9 @@ function shortcut_preprocess_page(&$variables) {
// Check if $link is already a shortcut and set $link_mode accordingly.
$shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(array('shortcut_set' => $shortcut_set->id()));
/** @var \Drupal\shortcut\ShortcutInterface $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();
break;
}
......
......@@ -64,7 +64,9 @@ public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Reques
$shortcut = $this->entityManager()->getStorage('shortcut')->create(array(
'title' => $name,
'shortcut_set' => $shortcut_set->id(),
'path' => $link,
'link' => array(
'uri' => $link,
),
));
try {
......
......@@ -12,13 +12,14 @@
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Url;
use Drupal\link\LinkItemInterface;
use Drupal\shortcut\ShortcutInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines the shortcut entity class.
*
* @property \Drupal\link\LinkItemInterface link
*
* @ContentEntityType(
* id = "shortcut",
* label = @Translation("Shortcut link"),
......@@ -87,67 +88,7 @@ public function setWeight($weight) {
* {@inheritdoc}
*/
public function getUrl() {
return new Url($this->getRouteName(), $this->getRouteParameters());
}
/**
* {@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());
return $this->link->first()->getUrl();
}
/**
......@@ -205,13 +146,22 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setLabel(t('Weight'))
->setDescription(t('Weight among shortcuts in the same shortcut set.'));
$fields['route_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Route name'))
->setDescription(t('The machine name of a defined Route this shortcut represents.'));
$fields['route_parameters'] = BaseFieldDefinition::create('map')
->setLabel(t('Route parameters'))
->setDescription(t('A serialized array of route parameters of this shortcut.'));
$fields['link'] = BaseFieldDefinition::create('link')
->setLabel(t('Path'))
->setDescription(t('The location this shortcut points to.'))
->setRequired(TRUE)
->setTranslatable(FALSE)
->setSettings(array(
'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')
->setLabel(t('Language'))
......@@ -224,16 +174,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
'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;
}
......
......@@ -52,7 +52,11 @@ public function form(array $form, FormStateInterface $form_state) {
foreach ($this->entity->getShortcuts() as $shortcut) {
$id = $shortcut->id();
$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'] = array(
'#type' => 'weight',
......
......@@ -27,7 +27,7 @@ public function getFormId() {
* {@inheritdoc}
*/
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 @@
namespace Drupal\shortcut;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the shortcut entity forms.
......@@ -25,76 +22,6 @@ class ShortcutForm extends ContentEntityForm {
*/
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}
*/
......
......@@ -60,42 +60,4 @@ public function setWeight($weight);
*/
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() {
protected function createEntity() {
// Create a "Llama" shortcut.
$shortcut = Shortcut::create(array(
'set' => 'default',
'shortcut_set' => 'default',
'title' => t('Llama'),
'weight' => 0,
'path' => 'admin',
'link' => ['uri' => 'admin'],
));
$shortcut->save();
......
......@@ -6,6 +6,9 @@
*/
namespace Drupal\shortcut\Tests;
use Drupal\Component\Utility\String;
use Drupal\Core\Url;
use Drupal\shortcut\Entity\Shortcut;
use Drupal\shortcut\Entity\ShortcutSet;
......@@ -37,42 +40,40 @@ public function testShortcutLinkAdd() {
$this->container->get('path.alias_storage')->save($path['source'], $path['alias']);
// Create some paths to test.
$test_cases = array(
array('path' => '', 'route_name' => '<front>'),
array('path' => '<front>', 'route_name' => '<front>'),
array('path' => 'admin', 'route_name' => 'system.admin'),
array('path' => 'admin/config/system/site-information', 'route_name' => 'system.site_information_settings'),
array('path' => 'node/' . $this->node->id() . '/edit', 'route_name' => 'entity.node.edit_form'),
array('path' => $path['alias'], 'route_name' => 'entity.node.canonical'),
array('path' => 'router_test/test2', 'route_name' => 'router_test.2'),
array('path' => 'router_test/test3/value', 'route_name' => 'router_test.3'),
);
$test_cases = [
'<front>',
'admin',
'admin/config/system/site-information',
'node/' . $this->node->id() . '/edit',
$path['alias'],
'router_test/test2',
'router_test/test3/value',
];
// Check that each new shortcut links where it should.
foreach ($test_cases as $test) {
foreach ($test_cases as $test_path) {
$title = $this->randomMachineName();
$form_data = array(
'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->assertResponse(200);
$saved_set = ShortcutSet::load($set->id());
$routes = $this->getShortcutInformation($saved_set, 'route_name');
$this->assertTrue(in_array($test['route_name'], $routes), 'Shortcut created: ' . $test['path']);
$this->assertLink($title, 0, 'Shortcut link found on the page.');
$paths = $this->getShortcutInformation($saved_set, 'link');
$test_path = $test_path != '<front>' ? $test_path : '';
$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());
// Test that saving and re-loading a shortcut preserves its values.
$shortcuts = $saved_set->getShortcuts();
foreach ($shortcuts as $entity) {
// Test the node routes with parameters.
if (strpos($entity->route_name->value, 'node.') === 0) {
$entity->save();
$loaded = Shortcut::load($entity->id());
$this->assertEqual($entity->route_name->value, $loaded->route_name->value);
$this->assertEqual($entity->get('route_parameters')->first()->getValue(), $loaded->get('route_parameters')->first()->getValue());
}
$this->assertEqual($entity->link->uri, $loaded->link->uri);
$this->assertEqual($entity->link->options, $loaded->link->options);
}
// Login as non admin user, to check that access is checked when creating
......@@ -81,15 +82,15 @@ public function testShortcutLinkAdd() {
$title = $this->randomMachineName();
$form_data = [
'title[0][value]' => $title,
'path' => 'admin',
'link[0][uri]' => 'admin',
];
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertResponse(200);
$this->assertRaw(t('The shortcut must correspond to a valid path on the site.'));
$this->assertRaw(t('The URL %url is not valid.', ['%url' => 'admin']));
$form_data = [
'title[0][value]' => $title,
'path' => 'node',
'link[0][uri]' => 'node',
];
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertLink($title, 0, 'Shortcut link found on the page.');
......@@ -136,7 +137,7 @@ public function testShortcutLinkRename() {
$shortcuts = $set->getShortcuts();
$shortcut = reset($shortcuts);
$this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $new_link_name, 'path' => $shortcut->path->value), t('Save'));
$this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $new_link_name, 'link[0][uri]' => $shortcut->link->uri), t('Save'));
$saved_set = ShortcutSet::load($set->id());
$titles = $this->getShortcutInformation($saved_set, 'title');
$this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name);
......@@ -154,10 +155,10 @@ public function testShortcutLinkChangePath() {
$shortcuts = $set->getShortcuts();
$shortcut = reset($shortcuts);
$this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $shortcut->getTitle(), 'path' => $new_link_path), t('Save'));
$this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $shortcut->getTitle(), 'link[0][uri]' => $new_link_path), t('Save'));
$saved_set = ShortcutSet::load($set->id());
$routes = $this->getShortcutInformation($saved_set, 'route_name');
$this->assertTrue(in_array('system.admin_config', $routes), 'Shortcut path changed: ' . $new_link_path);
$paths = $this->getShortcutInformation($saved_set, 'link');
$this->assertTrue(in_array($new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path);
$this->assertLinkByHref($new_link_path, 0, 'Shortcut with new path appears on the page.');
}
......
......@@ -62,18 +62,22 @@ protected function setUp() {
// Populate the default shortcut set.
$shortcut = Shortcut::create(array(
'set' => 'default',
'shortcut_set' => 'default',
'title' => t('Add content'),
'weight' => -20,
'path' => 'node/add',
'link' => array(
'uri' => 'node/add',
),
));
$shortcut->save();
$shortcut = Shortcut::create(array(
'set' => 'default',
'shortcut_set' => 'default',
'title' => t('All content'),
'weight' => -19,
'path' => 'admin/content',
'link' => array(
'uri' => 'admin/content',
),
));
$shortcut->save();
}
......@@ -111,7 +115,7 @@ function generateShortcutSet($label = '', $id = NULL) {
* @param string $key
* The array key indicating what information to extract from each link:
* - 'title': Extract shortcut titles.
* - 'path': Extract shortcut paths.
* - 'link': Extract shortcut paths.
* - 'id': Extract the shortcut ID.
*
* @return array
......@@ -121,8 +125,13 @@ function getShortcutInformation(ShortcutSetInterface $set, $key) {
$info = array();
\Drupal::entityManager()->getStorage('shortcut')->resetCache();
foreach ($set->getShortcuts() as $shortcut) {
if ($key == 'link') {
$info[] = $shortcut->link->uri;
}
else {
$info[] = $shortcut->{$key}->value;
}
}
return $info;
}
......
......@@ -25,6 +25,7 @@ class ShortcutTranslationUITest extends ContentTranslationUITest {
public static $modules = array(
'language',
'content_translation',
'link',
'shortcut',
'toolbar'
);
......@@ -49,7 +50,7 @@ protected function getTranslatorPermissions() {
* {@inheritdoc}
*/
protected function createEntity($values, $langcode, $bundle_name = NULL) {
$values['route_name'] = 'user.page';
$values['link']['uri'] = 'user';
return parent::createEntity($values, $langcode, $bundle_name);
}
......
......@@ -58,7 +58,7 @@ function standard_install() {
'shortcut_set' => 'default',
'title' => t('Add content'),
'weight' => -20,
'path' => 'node/add',
'link' => array('uri' => 'node/add'),
));
$shortcut->save();
......@@ -66,7 +66,7 @@ function standard_install() {
'shortcut_set' => 'default',
'title' => t('All content'),
'weight' => -