Commit e72ff704 authored by webchick's avatar webchick

Issue #916388 by disasm, podarok, dawehner, andypost, amateescu, Dave Reid:...

Issue #916388 by disasm, podarok, dawehner, andypost, amateescu, Dave Reid: Convert menu links into entities.
parent 167e70ba
......@@ -243,6 +243,11 @@ Locale module
Menu module
- ?
Menu Link module
- Andrei Mateescu 'amateescu' http://drupal.org/user/729614
- Károly Négyesi 'chx' http://drupal.org/user/9446
- @todo Anyone else from the menu system?
Node module
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- David Strauss 'David Strauss' http://drupal.org/user/93254
......
This diff is collapsed.
......@@ -389,7 +389,7 @@ function aggregator_save_category($edit) {
->execute();
$op = 'insert';
}
if (isset($op)) {
if (isset($op) && module_exists('menu_link')) {
menu_link_maintain('aggregator', $op, $link_path, $edit['title']);
}
}
......
......@@ -37,8 +37,8 @@ function testCategorizeFeedItem() {
$this->assertTrue(!empty($category), 'The category found in database.');
$link_path = 'aggregator/categories/' . $category->cid;
$menu_link = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path))->fetch();
$this->assertTrue(!empty($menu_link), 'The menu link associated with the category found in database.');
$menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path));
$this->assertTrue(!empty($menu_links), 'The menu link associated with the category found in database.');
$feed = $this->createFeed();
db_insert('aggregator_category_feed')
......
......@@ -3,5 +3,6 @@ description = Allows users to create and organize related content in an outline.
package = Core
version = VERSION
core = 8.x
dependencies[] = menu_link
dependencies[] = node
configure = admin/content/book/settings
......@@ -8,6 +8,8 @@
use Drupal\node\Plugin\Core\Entity\Node;
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
use Drupal\Core\Template\Attribute;
use Drupal\menu_link\Plugin\Core\Entity\MenuLink;
use Drupal\menu_link\MenuLinkStorageController;
/**
* Implements hook_help().
......@@ -558,7 +560,8 @@ function _book_update_outline(Node $node) {
}
}
if (menu_link_save($node->book)) {
$node->book = entity_create('menu_link', $node->book);
if ($node->book->save()) {
if ($new) {
// Insert new.
db_insert('book')
......@@ -926,7 +929,7 @@ function book_node_prepare(Node $node) {
* The depth limit for items in the parent select.
*/
function _book_parent_depth_limit($book_link) {
return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? menu_link_children_relative_depth($book_link) : 0);
return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($book_link) : 0);
}
/**
......
......@@ -105,12 +105,12 @@ function testMenuNodeFormWidget() {
$this->assertNoLink($node_title);
// Add a menu link to the Administration menu.
$item = array(
$item = entity_create('menu_link', array(
'link_path' => 'node/' . $node->nid,
'link_title' => $this->randomName(16),
'menu_name' => 'admin',
);
menu_link_save($item);
));
$item->save();
// Assert that disabled Administration menu is not shown on the
// node/$nid/edit page.
......@@ -127,12 +127,12 @@ function testMenuNodeFormWidget() {
// Create a second node.
$child_node = $this->drupalCreateNode(array('type' => 'article'));
// Assign a menu link to the second node, being a child of the first one.
$child_item = array(
$child_item = entity_create('menu_link', array(
'link_path' => 'node/'. $child_node->nid,
'link_title' => $this->randomName(16),
'plid' => $item['mlid'],
);
menu_link_save($child_item);
));
$child_item->save();
// Edit the first node.
$this->drupalGet('node/'. $node->nid .'/edit');
// Assert that it is not possible to set the parent of the first node to itself or the second node.
......
......@@ -78,12 +78,12 @@ function testMenu() {
$item = $this->getStandardMenuLink();
$old_title = $item['link_title'];
$this->modifyMenuLink($item);
$item = menu_link_load($item['mlid']);
$item = entity_load('menu_link', $item['mlid']);
// Verify that a change to the description is saved.
$description = $this->randomName(16);
$item['options']['attributes']['title'] = $description;
menu_link_save($item);
$saved_item = menu_link_load($item['mlid']);
$saved_item = entity_load('menu_link', $item['mlid']);
$this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
$this->resetMenuLink($item, $old_title);
}
......@@ -180,7 +180,7 @@ function deleteCustomMenu($menu) {
$this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted');
$this->assertFalse(menu_load($menu_name), 'Custom menu was deleted');
// Test if all menu links associated to the menu were removed from database.
$result = db_query("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
$result = entity_load_multiple_by_properties('menu_link', array('menu_name' => $menu_name));
$this->assertFalse($result, 'All menu links associated to the custom menu were deleted.');
}
......@@ -282,7 +282,9 @@ function testMenuQueryAndFragment() {
* @param string $link Link path.
* @param string $menu_name Menu name.
* @param string $weight Menu weight
* @return array Menu link created.
*
* @return \Drupal\menu_link\Plugin\Core\Entity\MenuLink $menu_link
* A menu link entity.
*/
function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'tools', $expanded = TRUE, $weight = '0') {
// View add menu link page.
......@@ -303,14 +305,14 @@ function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'tools', $expand
// Add menu link.
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertResponse(200);
// Unlike most other modules, there is no confirmation message displayed.
$this->assertText($title, 'Menu link was added');
$this->assertText('The menu link has been saved.');
$item = db_query('SELECT * FROM {menu_links} WHERE link_title = :title', array(':title' => $title))->fetchAssoc();
$this->assertTrue(t('Menu link was found in database.'));
$this->assertMenuLink($item['mlid'], array('menu_name' => $menu_name, 'link_path' => $link, 'has_children' => 0, 'plid' => $plid));
$menu_links = entity_load_multiple_by_properties('menu_link', array('link_title' => $title));
$menu_link = reset($menu_links);
$this->assertTrue('Menu link was found in database.');
$this->assertMenuLink($menu_link->id(), array('menu_name' => $menu_name, 'link_path' => $link, 'has_children' => 0, 'plid' => $plid));
return $item;
return $menu_link;
}
/**
......@@ -393,8 +395,7 @@ function modifyMenuLink(&$item) {
$edit['link_title'] = $title;
$this->drupalPost("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
$this->assertResponse(200);
// Unlike most other modules, there is no confirmation message displayed.
$this->assertText('The menu link has been saved.');
// Verify menu link.
$this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']);
$this->assertText($title, 'Menu link was edited');
......@@ -501,8 +502,8 @@ function enableMenuLink($item) {
*/
function assertMenuLink($mlid, array $expected_item) {
// Retrieve menu link.
$item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array(':mlid' => $mlid))->fetchAssoc();
$options = unserialize($item['options']);
$item = entity_load('menu_link', $mlid);
$options = $item->options;
if (!empty($options['query'])) {
$item['link_path'] .= '?' . drupal_http_build_query($options['query']);
}
......@@ -518,12 +519,21 @@ function assertMenuLink($mlid, array $expected_item) {
* Get standard menu link.
*/
private function getStandardMenuLink() {
// Retrieve menu link id of the Log out menu link, which will always be on the front page.
$mlid = db_query("SELECT mlid FROM {menu_links} WHERE module = 'system' AND router_path = 'user/logout'")->fetchField();
$mlid = 0;
// Retrieve menu link id of the Log out menu link, which will always be on
// the front page.
$query = entity_query('menu_link')
->condition('module', 'system')
->condition('router_path', 'user/logout');
$result = $query->execute();
if (!empty($result)) {
$mlid = reset($result);
}
$this->assertTrue($mlid > 0, 'Standard menu link id was found');
// Load menu link.
// Use api function so that link is translated for rendering.
$item = menu_link_load($mlid);
$item = entity_load('menu_link', $mlid);
$this->assertTrue((bool) $item, 'Standard menu link was loaded');
return $item;
}
......
This diff is collapsed.
......@@ -3,4 +3,5 @@ description = Allows administrators to customize the site navigation menu.
package = Core
version = VERSION
core = 8.x
dependencies[] = menu_link
configure = admin/structure/menu
This diff is collapsed.
<?php
/**
* @file
* Contains \Drupal\menu_link\MenuLinkFormController.
*/
namespace Drupal\menu_link;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityFormController;
/**
* Form controller for the node edit forms.
*/
class MenuLinkFormController extends EntityFormController {
/**
* Overrides EntityFormController::form().
*/
public function form(array $form, array &$form_state, EntityInterface $menu_link) {
// Since menu_link_load() no longer returns a translated and access checked
// item, do it here instead.
_menu_link_translate($menu_link);
if (!$menu_link->isNew()) {
// Get the human-readable menu title from the given menu name.
$titles = menu_get_menus();
$current_title = $titles[$menu_link->menu_name];
// Get the current breadcrumb and add a link to that menu's overview page.
$breadcrumb = menu_get_active_breadcrumb();
$breadcrumb[] = l($current_title, 'admin/structure/menu/manage/' . $menu_link->menu_name);
drupal_set_breadcrumb($breadcrumb);
}
$form['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
'#default_value' => $menu_link->link_title,
'#description' => t('The text to be used for this link in the menu.'),
'#required' => TRUE,
);
foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) {
$form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key});
}
// Any item created or edited via this interface is considered "customized".
$form['customized'] = array('#type' => 'value', '#value' => 1);
// We are not using url() when constructing this path because it would add
// $base_path.
$path = $menu_link->link_path;
if (isset($menu_link->options['query'])) {
$path .= '?' . drupal_http_build_query($menu_link->options['query']);
}
if (isset($menu_link->options['fragment'])) {
$path .= '#' . $menu_link->options['fragment'];
}
if ($menu_link->module == 'menu') {
$form['link_path'] = array(
'#type' => 'textfield',
'#title' => t('Path'),
'#maxlength' => 255,
'#default_value' => $path,
'#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
'#required' => TRUE,
);
}
else {
$form['_path'] = array(
'#type' => 'item',
'#title' => t('Path'),
'#description' => l($menu_link->link_title, $menu_link->href, $menu_link->options),
);
}
$form['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '',
'#rows' => 1,
'#description' => t('Shown when hovering over the menu link.'),
);
$form['enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Enabled'),
'#default_value' => !$menu_link->hidden,
'#description' => t('Menu links that are not enabled will not be listed in any menu.'),
);
$form['expanded'] = array(
'#type' => 'checkbox',
'#title' => t('Show as expanded'),
'#default_value' => $menu_link->expanded,
'#description' => t('If selected and this menu link has children, the menu will always appear expanded.'),
);
// Generate a list of possible parents (not including this link or descendants).
$options = menu_parent_options(menu_get_menus(), $menu_link);
$default = $menu_link->menu_name . ':' . $menu_link->plid;
if (!isset($options[$default])) {
$default = 'tools:0';
}
$form['parent'] = array(
'#type' => 'select',
'#title' => t('Parent link'),
'#default_value' => $default,
'#options' => $options,
'#description' => t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
'#attributes' => array('class' => array('menu-title-select')),
);
// Get number of items in menu so the weight selector is sized appropriately.
$delta = drupal_container()->get('plugin.manager.entity')
->getStorageController('menu_link')->countMenuLinks($menu_link->menu_name);
$form['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
// Old hardcoded value.
'#delta' => max($delta, 50),
'#default_value' => $menu_link->weight,
'#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'),
);
$form['langcode'] = array(
'#type' => 'language_select',
'#title' => t('Language'),
'#languages' => LANGUAGE_ALL,
'#default_value' => $menu_link->langcode,
);
return parent::form($form, $form_state, $menu_link);
}
/**
* Overrides EntityFormController::actions().
*/
protected function actions(array $form, array &$form_state) {
$element = parent::actions($form, $form_state);
$element['submit']['#button_type'] = 'primary';
$element['delete']['#access'] = $this->getEntity($form_state)->module == 'menu';
return $element;
}
/**
* Overrides EntityFormController::validate().
*/
public function validate(array $form, array &$form_state) {
$menu_link = $this->buildEntity($form, $form_state);
$normal_path = drupal_container()->get('path.alias_manager.cached')->getSystemPath($menu_link->link_path);
if ($menu_link->link_path != $normal_path) {
drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $menu_link->link_path, '%normal_path' => $normal_path)));
$menu_link->link_path = $normal_path;
}
if (!url_is_external($menu_link->link_path)) {
$parsed_link = parse_url($menu_link->link_path);
if (isset($parsed_link['query'])) {
$menu_link->options['query'] = drupal_get_query_array($parsed_link['query']);
}
else {
// Use unset() rather than setting to empty string
// to avoid redundant serialized data being stored.
unset($menu_link->options['query']);
}
if (isset($parsed_link['fragment'])) {
$menu_link->options['fragment'] = $parsed_link['fragment'];
}
else {
unset($menu_link->options['fragment']);
}
if (isset($parsed_link['path']) && $menu_link->link_path != $parsed_link['path']) {
$menu_link->link_path = $parsed_link['path'];
}
}
if (!trim($menu_link->link_path) || !drupal_valid_path($menu_link->link_path, TRUE)) {
form_set_error('link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path)));
}
parent::validate($form, $form_state);
}
/**
* Overrides EntityFormController::submit().
*/
public function submit(array $form, array &$form_state) {
// Build the menu link object from the submitted values.
$menu_link = parent::submit($form, $form_state);
// The value of "hidden" is the opposite of the value supplied by the
// "enabled" checkbox.
$menu_link->hidden = (int) !$menu_link->enabled;
unset($menu_link->enabled);
$menu_link->options['attributes']['title'] = $menu_link->description;
list($menu_link->menu_name, $menu_link->plid) = explode(':', $menu_link->parent);
return $menu_link;
}
/**
* Overrides EntityFormController::save().
*/
public function save(array $form, array &$form_state) {
$menu_link = $this->getEntity($form_state);
$saved = $menu_link->save();
if ($saved) {
drupal_set_message(t('The menu link has been saved.'));
$form_state['redirect'] = 'admin/structure/menu/manage/' . $menu_link->menu_name;
}
else {
drupal_set_message(t('There was an error saving the menu link.'), 'error');
$form_state['rebuild'] = TRUE;
}
}
/**
* Overrides EntityFormController::delete().
*/
public function delete(array $form, array &$form_state) {
$menu_link = $this->getEntity($form_state);
$form_state['redirect'] = 'admin/structure/menu/item/' . $menu_link->id() . '/delete';
}
}
<?php
/**
* @file
* Contains \Drupal\menu_link\Plugin\Core\Entity\MenuLink.
*/
namespace Drupal\menu_link\Plugin\Core\Entity;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Entity;
/**
* Defines the menu link entity class.
*
* @Plugin(
* id = "menu_link",
* label = @Translation("Menu link"),
* module = "menu_link",
* controller_class = "Drupal\menu_link\MenuLinkStorageController",
* form_controller_class = {
* "default" = "Drupal\menu_link\MenuLinkFormController"
* },
* static_cache = FALSE,
* base_table = "menu_links",
* uri_callback = "menu_link_uri",
* entity_keys = {
* "id" = "mlid",
* "label" = "link_title",
* "uuid" = "uuid"
* },
* bundles = {
* "menu_link" = {
* "label" = "Menu link",
* }
* }
* )
*/
class MenuLink extends Entity implements \ArrayAccess, ContentEntityInterface {
/**
* The link's menu name.
*
* @var string
*/
public $menu_name = 'tools';
/**
* The menu link ID.
*
* @var int
*/
public $mlid;
/**
* The menu link UUID.
*
* @var string
*/
public $uuid;
/**
* The parent link ID.
*
* @var int
*/
public $plid;
/**
* The Drupal path or external path this link points to.
*
* @var string
*/
public $link_path;
/**
* For links corresponding to a Drupal path (external = 0), this connects the
* link to a {menu_router}.path for joins.
*
* @var string
*/
public $router_path;
/**
* The entity label.
*
* @var string
*/
public $link_title = '';
/**
* A serialized array of options to be passed to the url() or l() function,
* such as a query string or HTML attributes.
*
* @var array
*/
public $options = array();
/**
* The name of the module that generated this link.
*
* @var string
*/
public $module = 'menu';
/**
* A flag for whether the link should be rendered in menus.
*
* @var int
*/
public $hidden = 0;
/**
* A flag to indicate if the link points to a full URL starting with a
* protocol, like http:// (1 = external, 0 = internal).
*
* @var int
*/
public $external;
/**
* Flag indicating whether any links have this link as a parent.
*
* @var int
*/
public $has_children = 0;
/**
* Flag for whether this link should be rendered as expanded in menus.
* Expanded links always have their child links displayed, instead of only
* when the link is in the active trail.
*
* @var int
*/
public $expanded = 0;
/**
* Link weight among links in the same menu at the same depth.
*
* @var int
*/
public $weight = 0;
/**
* The depth relative to the top level. A link with plid == 0 will have
* depth == 1.
*
* @var int
*/
public $depth;
/**
* A flag to indicate that the user has manually created or edited the link.
*
* @var int
*/
public $customized = 0;
/**
* The first entity ID in the materialized path.
*
* @var int
*
* @todo Investigate whether the p1, p2, .. pX properties can be moved to a
* single array property.
*/
public $p1;
/**
* The second entity ID in the materialized path.
*
* @var int
*/
public $p2;
/**
* The third entity ID in the materialized path.
*
* @var int
*/
public $p3;
/**
* The fourth entity ID in the materialized path.
*
* @var int
*/
public $p4;
/**
* The fifth entity ID in the materialized path.
*
* @var int
*/
public $p5;
/**
* The sixth entity ID in the materialized path.
*
* @var int
*/
public $p6;
/**
* The seventh entity ID in the materialized path.
*
* @var int
*/
public $p7;
/**
* The eighth entity ID in the materialized path.
*
* @var int
*/
public $p8;
/**
* The ninth entity ID in the materialized path.
*
* @var int
*/
public $p9;
/**
* The menu link modification timestamp.
*
* @var int
*/
public $updated = 0;
/**
* Overrides Entity::id().
*/
public function id() {
return $this->mlid;
}
/**