Commit cb22bbde authored by webchick's avatar webchick

Issue #2406749 by dawehner, YesCT, amateescu, hussainweb, kim.pepper,...

Issue #2406749 by dawehner, YesCT, amateescu, hussainweb, kim.pepper, RavindraSingh, pwolanin, Wim Leers, Gábor Hojtsy, yched, jibran: Use a link field for custom menu link
parent d42d61a7
......@@ -4,3 +4,5 @@ description: 'Allows administrators to create custom menu links.'
package: Core
version: VERSION
core: 8.x
dependencies:
- link
......@@ -12,11 +12,14 @@
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Url;
use Drupal\link\LinkItemInterface;
use Drupal\menu_link_content\MenuLinkContentInterface;
/**
* Defines the menu link content entity class.
*
* @property \Drupal\link\LinkItemInterface link
*
* @ContentEntityType(
* id = "menu_link_content",
* label = @Translation("Custom menu link"),
......@@ -69,53 +72,11 @@ public function getTitle() {
return $this->get('title')->value;
}
/**
* {@inheritdoc}
*/
public function getRouteName() {
return $this->get('route_name')->value;
}
/**
* {@inheritdoc}
*/
public function getRouteParameters() {
return $this->get('route_parameters')->first()->getValue();
}
/**
* {@inheritdoc}
*/
public function setRouteParameters(array $route_parameters) {
$this->set('route_parameters', array($route_parameters));
return $this;
}
/**
* {@inheritdoc}
*/
public function getUrl() {
return $this->get('url')->value ?: NULL;
}
/**
* {@inheritdoc}
*/
public function getUrlObject() {
if ($route_name = $this->getRouteName()) {
$url = new Url($route_name, $this->getRouteParameters(), $this->getOptions());
}
else {
$path = $this->getUrl();
if (isset($path)) {
$url = Url::fromUri($path);
}
else {
$url = new Url('<front>');
}
}
return $url;
return \Drupal::pathValidator()->getUrlIfValidWithoutAccessCheck($this->link->uri) ?: Url::fromRoute('<front>');
}
/**
......@@ -125,21 +86,6 @@ public function getMenuName() {
return $this->get('menu_name')->value;
}
/**
* {@inheritdoc}
*/
public function getOptions() {
return $this->get('options')->first()->getValue();
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options) {
$this->set('options', array($options));
return $this;
}
/**
* {@inheritdoc}
*/
......@@ -201,10 +147,18 @@ protected function getPluginDefinition() {
$definition = array();
$definition['class'] = 'Drupal\menu_link_content\Plugin\Menu\MenuLinkContent';
$definition['menu_name'] = $this->getMenuName();
$definition['route_name'] = $this->getRouteName();
$definition['route_parameters'] = $this->getRouteParameters();
$definition['url'] = $this->getUrl();
$definition['options'] = $this->getOptions();
if ($url_object = $this->getUrlObject()) {
if ($url_object->isExternal()) {
$definition['url'] = $url_object->getUri();
}
else {
$definition['route_name'] = $url_object->getRouteName();
$definition['route_parameters'] = $url_object->getRouteParameters();
}
$definition['options'] = $url_object->getOptions();
}
$definition['title'] = $this->getTitle();
$definition['description'] = $this->getDescription();
$definition['weight'] = $this->getWeight();
......@@ -327,23 +281,17 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDescription(t('The menu name. All links with the same menu name (such as "tools") are part of the same menu.'))
->setSetting('default_value', 'tools');
// @todo Use a link field https://www.drupal.org/node/2302205.
$fields['route_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Route name'))
->setDescription(t('The machine name of a defined Symfony Route this menu item represents.'));
$fields['route_parameters'] = BaseFieldDefinition::create('map')
->setLabel(t('Route parameters'))
->setDescription(t('A serialized array of route parameters of this menu link.'));
$fields['url'] = BaseFieldDefinition::create('uri')
->setLabel(t('External link url'))
->setDescription(t('The url of the link, in case you have an external link.'));
$fields['options'] = BaseFieldDefinition::create('map')
->setLabel(t('Options'))
->setDescription(t('A serialized array of URL options, such as a query string or HTML attributes.'))
->setSetting('default_value', array());
$fields['link'] = BaseFieldDefinition::create('link')
->setLabel(t('Link'))
->setDescription(t('The location this menu link points to.'))
->setRequired(TRUE)
->setSettings(array(
'default_value' => '',
'max_length' => 560,
'link_type' => LinkItemInterface::LINK_GENERIC,
'title' => DRUPAL_DISABLED,
))
;
$fields['external'] = BaseFieldDefinition::create('boolean')
->setLabel(t('External'))
......
......@@ -198,6 +198,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
if ($extracted) {
if ($extracted->isExternal()) {
$new_definition['url'] = $extracted->getUri();
$new_definition['options'] = $extracted->getOptions();
}
else {
$new_definition['route_name'] = $extracted->getRouteName();
......@@ -219,33 +220,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state)
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// We always show the internal path here.
/** @var \Drupal\Core\Url $url */
$url = $this->getEntity()->getUrlObject();
if ($url->isExternal()) {
$default_value = $url->toString();
}
elseif ($url->getRouteName() == '<front>') {
// The default route for new entities is <front>, but we just want an
// empty form field.
$default_value = $this->getEntity()->isNew() ? '' : '<front>';
}
else {
// @todo Url::getInternalPath() calls UrlGenerator::getPathFromRoute()
// which need a replacement since it is deprecated.
// https://www.drupal.org/node/2307061
$default_value = $url->getInternalPath();
}
// @todo Add a helper method to Url to render just the query string and
// fragment. https://www.drupal.org/node/2305013
$options = $url->getOptions();
if (isset($options['query'])) {
$default_value .= $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : '';
}
if (isset($options['fragment']) && $options['fragment'] !== '') {
$default_value .= '#' . $options['fragment'];
}
$default_value = $this->getEntity()->link->uri;
$form['url'] = array(
'#title' => $this->t('Link path'),
......@@ -299,10 +274,7 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
$entity->enabled->value = (bool) $new_definition['enabled'];
$entity->expanded->value = $new_definition['expanded'];
$entity->url->value = $new_definition['url'];
$entity->route_name->value = $new_definition['route_name'];
$entity->setRouteParameters($new_definition['route_parameters']);
$entity->setOptions($new_definition['options']);
$entity->link->uri = $form_state->getValue('url');
return $entity;
}
......
......@@ -64,10 +64,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
else {
// If there is a URL, this is an external link so always accessible.
$access = AccessResult::allowed()->cachePerRole()->cacheUntilEntityChanges($entity);
if (!$entity->getUrl()) {
// We allow access, but only if the link is accessible as well.
$link_access = $this->accessManager->checkNamedRoute($entity->getRouteName(), $entity->getRouteParameters(), $account, TRUE);
return $access->andIf($link_access);
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */
// We allow access, but only if the link is accessible as well.
if (($url_object = $entity->getUrlObject()) && $url_object->isRouted()) {
$link_access = $this->accessManager->checkNamedRoute($url_object->getRouteName(), $url_object->getRouteParameters(), $account, TRUE);
$access = $access->andIf($link_access);
}
return $access;
}
......
......@@ -28,46 +28,6 @@ public function setInsidePlugin();
*/
public function getTitle();
/**
* Gets the route name of the menu link.
*
* @return string|NULL
* Returns the route name, or NULL if it is an external link.
*/
public function getRouteName();
/**
* Gets the route parameters of the menu link content entity.
*
* @return array
* The route parameters, or an empty array.
*/
public function getRouteParameters();
/**
* Sets the route parameters of the custom menu link.
*
* @param array $route_parameters
* The route parameters, usually derived from the path entered by the
* administrator. For example, for a link to a node with route
* 'entity.node.canonical' the route needs the node ID as a parameter:
* @code
* array('node' => 2)
* @endcode
*
* @return $this
*/
public function setRouteParameters(array $route_parameters);
/**
* Gets the external URL.
*
* @return string|NULL
* Returns the external URL if the menu link points to an external URL,
* otherwise NULL.
*/
public function getUrl();
/**
* Gets the url object pointing to the URL of the menu link content entity.
*
......@@ -84,24 +44,6 @@ public function getUrlObject();
*/
public function getMenuName();
/**
* Gets the options for the menu link content entity.
*
* @return array
* The options that may be passed to the URL generator.
*/
public function getOptions();
/**
* Sets the query options of the menu link content entity.
*
* @param array $options
* The new option.
*
* @return $this
*/
public function setOptions(array $options);
/**
* Gets the description of the menu link for the UI.
*
......
......@@ -67,14 +67,14 @@ function createLinkHierarchy($module = 'menu_test') {
);
$parent = $base_options + array(
'route_name' => 'menu_test.hierarchy_parent',
'link' => ['uri' => 'menu-test/hierarchy/parent'],
);
$link = entity_create('menu_link_content', $parent);
$link->save();
$links['parent'] = $link->getPluginId();
$child_1 = $base_options + array(
'route_name' => 'menu_test.hierarchy_parent_child',
'link' => ['uri' => 'menu-test/hierarchy/parent/child'],
'parent' => $links['parent'],
);
$link = entity_create('menu_link_content', $child_1);
......@@ -82,7 +82,7 @@ function createLinkHierarchy($module = 'menu_test') {
$links['child-1'] = $link->getPluginId();
$child_1_1 = $base_options + array(
'route_name' => 'menu_test.hierarchy_parent_child2',
'link' => ['uri' => 'menu-test/hierarchy/parent/child2/child'],
'parent' => $links['child-1'],
);
$link = entity_create('menu_link_content', $child_1_1);
......@@ -90,7 +90,7 @@ function createLinkHierarchy($module = 'menu_test') {
$links['child-1-1'] = $link->getPluginId();
$child_1_2 = $base_options + array(
'route_name' => 'menu_test.hierarchy_parent_child2',
'link' => ['uri' => 'menu-test/hierarchy/parent/child2/child'],
'parent' => $links['child-1'],
);
$link = entity_create('menu_link_content', $child_1_2);
......@@ -98,7 +98,7 @@ function createLinkHierarchy($module = 'menu_test') {
$links['child-1-2'] = $link->getPluginId();
$child_2 = $base_options + array(
'route_name' => 'menu_test.hierarchy_parent_child',
'link' => ['uri' => 'menu-test/hierarchy/parent/child'],
'parent' => $links['parent'],
);
$link = entity_create('menu_link_content', $child_2);
......@@ -136,7 +136,7 @@ public function testCreateLink() {
$options = array(
'title' => 'Test Link',
);
$link->setOptions($options);
$link->link->options = $options;
$link->changed->value = REQUEST_TIME - 5;
$link->save();
// Make sure the changed timestamp is updated.
......
......@@ -57,7 +57,7 @@ protected function getAdministratorPermissions() {
*/
protected function createEntity($values, $langcode, $bundle_name = NULL) {
$values['menu_name'] = 'tools';
$values['route_name'] = 'entity.menu.collection';
$values['link']['uri'] = 'admin/structure/menu';
$values['title'] = 'Test title';
return parent::createEntity($values, $langcode, $bundle_name);
......@@ -70,7 +70,7 @@ public function testTranslationLinkOnMenuEditForm() {
$this->drupalGet('admin/structure/menu/manage/tools');
$this->assertNoLink(t('Translate'));
$menu_link_content = MenuLinkContent::create(['menu_name' => 'tools', 'route_name' => 'entity.menu.collection']);
$menu_link_content = MenuLinkContent::create(['menu_name' => 'tools', 'link' => ['uri' => 'admin/structure/menu']]);
$menu_link_content->save();
$this->drupalGet('admin/structure/menu/manage/tools');
$this->assertLink(t('Translate'));
......
......@@ -162,8 +162,9 @@ function menu_ui_node_save(EntityInterface $node) {
$entity = entity_create('menu_link_content', array(
'title' => trim($definition['title']),
'description' => trim($definition['description']),
'route_name' => 'entity.node.canonical',
'route_parameters' => array('node' => $node->id()),
// @todo Use entity: in the URI.
// https://www.drupal.org/node/2417367
'link' => ['uri' => 'node/' . $node->id()],
'menu_name' => $definition['menu_name'],
'parent' => $definition['parent'],
'weight' => isset($definition['weight']) ? $definition['weight'] : 0,
......@@ -212,8 +213,8 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
$type_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', array('main'));
if (in_array($menu_name, $type_menus)) {
$query = \Drupal::entityQuery('menu_link_content')
->condition('route_name', 'entity.node.canonical')
->condition('route_parameters', serialize(array('node' => $node->id())))
// @todo Use link.uri once https://www.drupal.org/node/2391217 is in.
->condition('link__uri', 'node/' . $node->id())
->condition('menu_name', $menu_name)
->sort('id', 'ASC')
->range(0, 1);
......@@ -224,8 +225,8 @@ function menu_ui_node_prepare_form(NodeInterface $node, $operation, FormStateInt
// Check all allowed menus if a link does not exist in the default menu.
if (!$id && !empty($type_menus)) {
$query = \Drupal::entityQuery('menu_link_content')
->condition('route_name', 'entity.node.canonical')
->condition('route_parameters', serialize(array('node' => $node->id())))
// @todo Use link.uri once https://www.drupal.org/node/2391217 is in.
->condition('link__uri', 'node/' . $node->id())
->condition('menu_name', array_values($type_menus), 'IN')
->sort('id', 'ASC')
->range(0, 1);
......
......@@ -106,7 +106,7 @@ function testMenu() {
foreach ($this->items as $item) {
// Paths were set as 'node/$nid'.
$node = Node::load($item->getRouteParameters()['node']);
$node = Node::load(str_replace('node/', '', $item->link->uri));
$this->verifyMenuLink($item, $node);
}
......
......@@ -22,15 +22,8 @@ process:
plugin: migration
migration: d6_menu
source: menu_name
route:
plugin: route
source:
- link_path
- options
route_name: @route/route_name
route_parameters: @route/route_parameters
url: @route/url
options: @route/options
'link/uri': link_path
'link/options': options
external: external
weight: weight
expanded: expanded
......
......@@ -376,7 +376,7 @@ public function load() {
'link_path' => 'admin',
'router_path' => 'admin',
'link_title' => 'Test 2',
'options' => 'a:2:{s:5:"query";s:7:"foo=bar";s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 2";}}',
'options' => 'a:2:{s:5:"query";a:1:{s:3:"foo";s:3:"bar";}s:10:"attributes";a:1:{s:5:"title";a:1:{i:0;s:16:"Test menu link 2";}}}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
......
......@@ -54,36 +54,30 @@ public function testMenuLinks() {
$this->assertIdentical($menu_link->getTitle(), 'Test 1');
$this->assertIdentical($menu_link->getMenuName(), 'secondary-links');
$this->assertIdentical($menu_link->getDescription(), 'Test menu link 1');
$this->assertIdentical($menu_link->getURL(), null);
$this->assertIdentical($menu_link->isEnabled(), TRUE);
$this->assertIdentical($menu_link->isExpanded(), FALSE);
$this->assertIdentical(serialize($menu_link->getOptions()), 'a:2:{s:5:"query";a:0:{}s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 1";}}');
$this->assertIdentical($menu_link->getRouteName(), 'user.login');
$this->assertIdentical($menu_link->getRouteParameters(), array());
$this->assertIdentical($menu_link->link->options, ['attributes' => ['title' => 'Test menu link 1']]);
$this->assertIdentical($menu_link->link->uri, 'user/login');
$this->assertIdentical($menu_link->getWeight(), 15);
$menu_link = entity_load('menu_link_content', 139);
$this->assertIdentical($menu_link->getTitle(), 'Test 2');
$this->assertIdentical($menu_link->getMenuName(), 'secondary-links');
$this->assertIdentical($menu_link->getDescription(), 'Test menu link 2');
$this->assertIdentical($menu_link->getURL(), null);
$this->assertIdentical($menu_link->isEnabled(), TRUE);
$this->assertIdentical($menu_link->isExpanded(), TRUE);
$this->assertIdentical(serialize($menu_link->getOptions()), 'a:2:{s:5:"query";a:1:{s:3:"foo";s:3:"bar";}s:10:"attributes";a:1:{s:5:"title";s:16:"Test menu link 2";}}');
$this->assertIdentical($menu_link->getRouteName(), 'system.admin');
$this->assertIdentical($menu_link->getRouteParameters(), array());
$this->assertIdentical($menu_link->link->options, ['query' => ['foo' => 'bar'], 'attributes' => ['title' => ['Test menu link 2']]]);
$this->assertIdentical($menu_link->link->uri, 'admin');
$this->assertIdentical($menu_link->getWeight(), 12);
$menu_link = entity_load('menu_link_content', 140);
$this->assertIdentical($menu_link->getTitle(), 'Drupal.org');
$this->assertIdentical($menu_link->getMenuName(), 'secondary-links');
$this->assertIdentical($menu_link->getDescription(), '');
$this->assertIdentical($menu_link->getURL(), 'http://drupal.org');
$this->assertIdentical($menu_link->isEnabled(), TRUE);
$this->assertIdentical($menu_link->isExpanded(), FALSE);
$this->assertIdentical(serialize($menu_link->getOptions()), 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}}');
$this->assertIdentical($menu_link->getRouteName(), NULL);
$this->assertIdentical($menu_link->getRouteParameters(), array());
$this->assertIdentical($menu_link->link->options, ['attributes' => ['title' => '']]);
$this->assertIdentical($menu_link->link->uri, 'http://drupal.org');
$this->assertIdentical($menu_link->getWeight(), 0);
}
......
......@@ -39,6 +39,7 @@ class SystemMenuBlockTest extends KernelTestBase {
'menu_link_content',
'field',
'user',
'link',
);
/**
......
......@@ -247,8 +247,8 @@ function testBreadCrumbs() {
$this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
$menu_links = entity_load_multiple_by_properties('menu_link_content', array(
'title' => $edit['title[0][value]'],
'route_name' => 'entity.taxonomy_term.canonical',
'route_parameters' => serialize(array('taxonomy_term' => $term->id())),
// @todo Use link.uri once https://www.drupal.org/node/2391217 is in.
'link__uri' => 'taxonomy/term/' . $term->id(),
));
$tags[$name]['link'] = reset($menu_links);
$parent_mlid = $tags[$name]['link']->getPluginId();
......
......@@ -45,6 +45,7 @@ class MenuLinkTreeTest extends KernelTestBase {
'menu_test',
'menu_link_content',
'field',
'link',
);
/**
......
......@@ -40,7 +40,7 @@ class MenuTreeStorageTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('system', 'menu_link_content');
public static $modules = array('system');
/**
* {@inheritdoc}
......@@ -50,7 +50,6 @@ protected function setUp() {
$this->treeStorage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'menu_tree');
$this->connection = $this->container->get('database');
$this->installEntitySchema('menu_link_content');
}
/**
......@@ -302,30 +301,22 @@ public function testLoadTree() {
* Tests finding the subtree height with content menu links.
*/
public function testSubtreeHeight() {
$storage = \Drupal::entityManager()->getStorage('menu_link_content');
// root
// - child1
// -- child2
// --- child3
// ---- child4
$root = $storage->create(array('route_name' => 'menu_test.menu_name_test', 'menu_name' => 'menu1', 'bundle' => 'menu_link_content'));
$root->save();
$child1 = $storage->create(array('route_name' => 'menu_test.menu_name_test', 'menu_name' => 'menu1', 'bundle' => 'menu_link_content', 'parent' => $root->getPluginId()));
$child1->save();
$child2 = $storage->create(array('route_name' => 'menu_test.menu_name_test', 'menu_name' => 'menu1', 'bundle' => 'menu_link_content', 'parent' => $child1->getPluginId()));
$child2->save();
$child3 = $storage->create(array('route_name' => 'menu_test.menu_name_test', 'menu_name' => 'menu1', 'bundle' => 'menu_link_content', 'parent' => $child2->getPluginId()));
$child3->save();
$child4 = $storage->create(array('route_name' => 'menu_test.menu_name_test', 'menu_name' => 'menu1', 'bundle' => 'menu_link_content', 'parent' => $child3->getPluginId()));
$child4->save();
$this->assertEqual($this->treeStorage->getSubtreeHeight($root->getPluginId()), 5);
$this->assertEqual($this->treeStorage->getSubtreeHeight($child1->getPluginId()), 4);
$this->assertEqual($this->treeStorage->getSubtreeHeight($child2->getPluginId()), 3);
$this->assertEqual($this->treeStorage->getSubtreeHeight($child3->getPluginId()), 2);
$this->assertEqual($this->treeStorage->getSubtreeHeight($child4->getPluginId()), 1);
$this->addMenuLink('root');
$this->addMenuLink('child1', 'root');
$this->addMenuLink('child2', 'child1');
$this->addMenuLink('child3', 'child2');
$this->addMenuLink('child4', 'child3');
$this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment