diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0f252ca..e4dcd65 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -6172,8 +6172,6 @@ function drupal_sort_weight($a, $b) {
/**
* Sorts a structured array by 'title' key (no # prefix).
*
- * Callback for uasort() within system_admin_index().
- *
* @param $a
* First item for comparison. The compared items should be associative arrays
* that optionally include a 'title' key.
@@ -6191,6 +6189,31 @@ function drupal_sort_title($a, $b) {
}
/**
+ * Sorts a structured array using the 'label()' method of its elements.
+ *
+ * Callback for uasort() within system_admin_index().
+ *
+ * @param Drupal\Core\Entity\EntityInterface $a
+ * First item for comparison.
+ * @param Drupal\Core\Entity\EntityInterface $b
+ * Second item for comparison.
+ *
+ * @return int
+ * An integer used by uasort().
+ */
+function drupal_sort_label($a, $b) {
+ $labelb = $b->label();
+ if (empty($labelb)) {
+ return -1;
+ }
+ $labela = $a->label();
+ if (empty($labela)) {
+ return 1;
+ }
+ return strcasecmp($labela, $labelb);
+}
+
+/**
* Checks if the key is a property.
*/
function element_property($key) {
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 5a5e544..f525b25 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -5,8 +5,11 @@
* API for the Drupal menu system.
*/
+use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Template\Attribute;
+use Drupal\menu_link\MenuLink;
+use Drupal\menu_link\MenuLinkStorageController;
/**
* @defgroup menu Menu system
@@ -264,6 +267,9 @@
/**
* The maximum depth of a menu links tree - matches the number of p columns.
+ *
+ * @todo Move this constant to MenuLinkStorageController along with all the tree
+ * functionality.
*/
const MENU_MAX_DEPTH = 9;
@@ -487,12 +493,12 @@ function menu_get_item($path = NULL, $router_item = NULL) {
/**
* Loads objects into the map as defined in the $item['load_functions'].
*
- * @param $item
- * A menu router or menu link item
- * @param $map
+ * @param array $item
+ * A menu router item.
+ * @param array $map
* An array of path arguments (ex: array('node', '5'))
*
- * @return
+ * @return bool
* Returns TRUE for success, FALSE if an object cannot be loaded.
* Names of object loading functions are placed in $item['load_functions'].
* Loaded objects are placed in $map[]; keys are the same as keys in the
@@ -555,11 +561,11 @@ function _menu_load_objects(&$item, &$map) {
}
/**
- * Checks access to a menu item using the access callback.
+ * Checks access to a menu router item using the access callback.
*
- * @param $item
- * A menu router or menu link item
- * @param $map
+ * @param array $item
+ * A menu router item.
+ * @param array $map
* An array of path arguments (ex: array('node', '5'))
*
* @return
@@ -594,14 +600,11 @@ function _menu_check_access(&$item, $map) {
* by the current user.
*
* @param $item
- * A menu router item or a menu link item.
+ * A menu router item.
* @param $map
* The path as an array with objects already replaced. E.g., for path
* node/123 $map would be array('node', $node) where $node is the node
* object for node 123.
- * @param $link_translate
- * TRUE if we are translating a menu link item; FALSE if we are
- * translating a menu router item.
*
* @return
* No return value.
@@ -610,52 +613,41 @@ function _menu_check_access(&$item, $map) {
* TRUE.
* $item['description'] is computed using $item['description_callback'] if
* specified; otherwise it is translated using t().
- * When doing link translation and the $item['options']['attributes']['title']
- * (link title attribute) matches the description, it is translated as well.
*/
-function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
+function _menu_item_localize(&$item, $map) {
$title_callback = $item['title_callback'];
$item['localized_options'] = $item['options'];
// All 'class' attributes are assumed to be an array during rendering, but
// links stored in the database may use an old string value.
// @todo In order to remove this code we need to implement a database update
// including unserializing all existing link options and running this code
- // on them, as well as adding validation to menu_link_save().
+ // on them, as well as adding validation to
+ // Drupal\menu_link\MenuLinkStorageController::save().
if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
$item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
}
- // If we are translating the title of a menu link, and its title is the same
- // as the corresponding router item, then we can use the title information
- // from the router. If it's customized, then we need to use the link title
- // itself; can't localize.
- // If we are translating a router item (tabs, page, breadcrumb), then we
- // can always use the information from the router item.
- if (!$link_translate || ($item['title'] == $item['link_title'])) {
- // t() is a special case. Since it is used very close to all the time,
- // we handle it directly instead of using indirect, slower methods.
- if ($title_callback == 't') {
- if (empty($item['title_arguments'])) {
- $item['title'] = t($item['title']);
- }
- else {
- $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
- }
+
+ // t() is a special case. Since it is used very close to all the time,
+ // we handle it directly instead of using indirect, slower methods.
+ if ($title_callback == 't') {
+ if (empty($item['title_arguments'])) {
+ $item['title'] = t($item['title']);
}
- elseif ($title_callback) {
- if (empty($item['title_arguments'])) {
- $item['title'] = $title_callback($item['title']);
- }
- else {
- $item['title'] = call_user_func_array($title_callback, menu_unserialize($item['title_arguments'], $map));
- }
- // Avoid calling check_plain again on l() function.
- if ($title_callback == 'check_plain') {
- $item['localized_options']['html'] = TRUE;
- }
+ else {
+ $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
}
}
- elseif ($link_translate) {
- $item['title'] = $item['link_title'];
+ elseif ($title_callback) {
+ if (empty($item['title_arguments'])) {
+ $item['title'] = $title_callback($item['title']);
+ }
+ else {
+ $item['title'] = call_user_func_array($title_callback, menu_unserialize($item['title_arguments'], $map));
+ }
+ // Avoid calling check_plain again on l() function.
+ if ($title_callback == 'check_plain') {
+ $item['localized_options']['html'] = TRUE;
+ }
}
// Translate description, see the motivation above.
@@ -686,9 +678,10 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
}
// If the title and description are the same, use the translated description
// as a localized title.
- if ($link_translate && isset($original_description) && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
- $item['localized_options']['attributes']['title'] = $item['description'];
- }
+ // @todo Take care of 'localized_options' later.
+// if (isset($original_description) && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
+// $item['localized_options']['attributes']['title'] = $item['description'];
+// }
}
/**
@@ -991,7 +984,9 @@ function menu_tree_output($tree) {
// Pull out just the menu links we are going to render so that we
// get an accurate count for the first/last classes.
foreach ($tree as $data) {
- if ($data['link']['access'] && !$data['link']['hidden']) {
+ if ($data['link']->access() && !$data['link']->hidden) {
+ // @todo Remove this hack in the great localized_options conversion.
+ $data['link']->localized_options = $data['link']->options;
$items[] = $data;
}
}
@@ -1009,45 +1004,46 @@ function menu_tree_output($tree) {
// Set a class for the
-tag. Since $data['below'] may contain local
// tasks, only set 'expanded' class if the link also has children within
// the current menu.
- if ($data['link']['has_children'] && $data['below']) {
+ if ($data['link']->has_children && $data['below']) {
$class[] = 'expanded';
}
- elseif ($data['link']['has_children']) {
+ elseif ($data['link']->has_children) {
$class[] = 'collapsed';
}
else {
$class[] = 'leaf';
}
// Set a class if the link is in the active trail.
- if ($data['link']['in_active_trail']) {
+ if ($data['link']->in_active_trail) {
$class[] = 'active-trail';
- $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
+ $data['link']->localized_options['attributes']['class'][] = 'active-trail';
}
// Normally, l() compares the href of every link with the current path and
// sets the active class accordingly. But local tasks do not appear in menu
// trees, so if the current path is a local task, and this link is its
// tab root, then we have to set the class manually.
- if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != current_path()) {
- $data['link']['localized_options']['attributes']['class'][] = 'active';
+ if ($data['link']->href == $router_item['tab_root_href'] && $data['link']->href != current_path()) {
+ $data['link']->localized_options['attributes']['class'][] = 'active';
}
// Allow menu-specific theme overrides.
- $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
+ $element['#theme'] = 'menu_link__' . strtr($data['link']->menu_name, '-', '_');
$element['#attributes']['class'] = $class;
- $element['#title'] = $data['link']['title'];
- $element['#href'] = $data['link']['href'];
- $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
+ $element['#title'] = $data['link']->label();
+ $element['#href'] = $data['link']->href;
+ // @todo Deal with 'localized_options' later.
+ $element['#localized_options'] = !empty($data['link']->localized_options) ? $data['link']->localized_options : array();
$element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
$element['#original_link'] = $data['link'];
- // Index using the link's unique mlid.
- $build[$data['link']['mlid']] = $element;
+ // Index using the link's unique id.
+ $build[$data['link']->id()] = $element;
}
if ($build) {
// Make sure drupal_render() does not re-order the links.
$build['#sorted'] = TRUE;
// Add the theme wrapper for outer markup.
// Allow menu-specific theme overrides.
- $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
+ $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']->menu_name, '-', '_');
}
return $build;
@@ -1078,7 +1074,7 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
$language_interface = language(LANGUAGE_TYPE_INTERFACE);
// Use $mlid as a flag for whether the data being loaded is for the whole tree.
- $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
+ $mlid = (isset($link) && $link->id()) ? $link->id() : 0;
// Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
$cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->langcode . ':' . (int) $max_depth;
@@ -1101,8 +1097,8 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
// p columns and 0 (the top level) with the plid values of other links.
$parents = array(0);
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
- if (!empty($link["p$i"])) {
- $parents[] = $link["p$i"];
+ if (!empty($link->{"p$i"})) {
+ $parents[] = $link->{"p$i"};
}
}
$tree_parameters['expanded'] = $parents;
@@ -1237,12 +1233,12 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
// active trail, if it resides in the requested menu. Otherwise,
// we'd needlessly re-run _menu_build_tree() queries for every menu
// on every page.
- if ($active_link['menu_name'] == $menu_name) {
+ if ($active_link->menu_name == $menu_name) {
// Use all the coordinates, except the last one because there
// can be no child beyond the last column.
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
- if ($active_link['p' . $i]) {
- $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
+ if ($active_link->{"p$i"}) {
+ $active_trail[$active_link->{"p$i"}] = $active_link->{"p$i"};
}
}
// If we are asked to build links for the active trail only, skip
@@ -1260,18 +1256,19 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
// Collect all the links set to be expanded, and then add all of
// their children to the list as well.
do {
- $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
- ->fields('menu_links', array('mlid'))
- ->condition('menu_name', $menu_name)
- ->condition('expanded', 1)
- ->condition('has_children', 1)
- ->condition('plid', $parents, 'IN')
- ->condition('mlid', $parents, 'NOT IN')
- ->execute();
+ $query = menu_link_query()
+ ->propertyCondition('menu_name', $menu_name)
+ ->propertyCondition('expanded', 1)
+ ->propertyCondition('has_children', 1)
+ ->propertyCondition('plid', $parents, 'IN')
+ ->propertyCondition('mlid', $parents, 'NOT IN');
+ $result = $query->execute();
$num_rows = FALSE;
- foreach ($result as $item) {
- $parents[$item['mlid']] = $item['mlid'];
- $num_rows = TRUE;
+ if (!empty($result['menu_link'])) {
+ foreach ($result['menu_link'] as $item) {
+ $parents[$item->entity_id] = $item->entity_id;
+ $num_rows = TRUE;
+ }
}
} while ($num_rows);
}
@@ -1360,61 +1357,38 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
}
if (!isset($trees[$tree_cid])) {
- // Select the links from the table, and recursively build the tree. We
- // LEFT JOIN since there is no match in {menu_router} for an external
- // link.
- $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
- $query->addTag('translatable');
- $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
- $query->fields('ml');
- $query->fields('m', array(
- 'load_functions',
- 'to_arg_functions',
- 'access_callback',
- 'access_arguments',
- 'page_callback',
- 'page_arguments',
- 'tab_parent',
- 'tab_root',
- 'title',
- 'title_callback',
- 'title_arguments',
- 'theme_callback',
- 'theme_arguments',
- 'type',
- 'description',
- 'description_callback',
- 'description_arguments',
- ));
+ $links = array();
+ $query = menu_link_query();
for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
- $query->orderBy('p' . $i, 'ASC');
+ $query->propertyOrderBy('p' . $i, 'ASC');
}
- $query->condition('ml.menu_name', $menu_name);
+ $query->propertyCondition('menu_name', $menu_name);
if (!empty($parameters['expanded'])) {
- $query->condition('ml.plid', $parameters['expanded'], 'IN');
+ $query->propertyCondition('plid', $parameters['expanded'], 'IN');
}
elseif (!empty($parameters['only_active_trail'])) {
- $query->condition('ml.mlid', $parameters['active_trail'], 'IN');
+ $query->entityCondition('entity_id', $parameters['active_trail'], 'IN');
}
$min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
if ($min_depth != 1) {
- $query->condition('ml.depth', $min_depth, '>=');
+ $query->propertyCondition('depth', $min_depth, '>=');
}
if (isset($parameters['max_depth'])) {
- $query->condition('ml.depth', $parameters['max_depth'], '<=');
+ $query->propertyCondition('depth', $parameters['max_depth'], '<=');
}
// Add custom query conditions, if any were passed.
if (isset($parameters['conditions'])) {
foreach ($parameters['conditions'] as $column => $value) {
- $query->condition($column, $value);
+ $query->propertyCondition($column, $value);
}
}
- // Build an ordered array of links using the query result object.
- $links = array();
- foreach ($query->execute() as $item) {
- $links[] = $item;
+ $result = $query->execute();
+
+ if (!empty($result['menu_link'])) {
+ $links = menu_link_load_multiple(array_keys($result['menu_link']));
}
+
$active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
$data['tree'] = menu_tree_data($links, $active_trail, $min_depth);
$data['node_links'] = array();
@@ -1438,11 +1412,11 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
*/
function menu_tree_collect_node_links(&$tree, &$node_links) {
foreach ($tree as $key => $v) {
- if ($tree[$key]['link']['router_path'] == 'node/%') {
- $nid = substr($tree[$key]['link']['link_path'], 5);
+ if ($tree[$key]['link']->router_path == 'node/%') {
+ $nid = substr($tree[$key]['link']->link_path, 5);
if (is_numeric($nid)) {
- $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
- $tree[$key]['link']['access'] = FALSE;
+ $node_links[$nid][$tree[$key]['link']->id()] = &$tree[$key]['link'];
+ $tree[$key]['link']->access = FALSE;
}
}
if ($tree[$key]['below']) {
@@ -1471,7 +1445,7 @@ function menu_tree_check_access(&$tree, $node_links = array()) {
$nids = $select->execute()->fetchCol();
foreach ($nids as $nid) {
foreach ($node_links[$nid] as $mlid => $link) {
- $node_links[$nid][$mlid]['access'] = TRUE;
+ $node_links[$nid][$mlid]->access = TRUE;
}
}
}
@@ -1485,15 +1459,13 @@ function _menu_tree_check_access(&$tree) {
$new_tree = array();
foreach ($tree as $key => $v) {
$item = &$tree[$key]['link'];
- _menu_link_translate($item);
- if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
+ if ($item->access() || ($item->in_active_trail && strpos($item->href, '%') !== FALSE)) {
if ($tree[$key]['below']) {
_menu_tree_check_access($tree[$key]['below']);
}
// The weights are made a uniform 5 digits by adding 50000 as an offset.
- // After _menu_link_translate(), $item['title'] has the localized link title.
- // Adding the mlid to the end of the index insures that it is unique.
- $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
+ // Adding the entity id to the end of the index insures that it is unique.
+ $new_tree[(50000 + $item->weight) . ' ' . $item->label() . ' ' . $item->id()] = $tree[$key];
}
}
// Sort siblings in the tree based on the weights and localized titles.
@@ -1541,27 +1513,27 @@ function menu_tree_data(array $links, array $parents = array(), $depth = 1) {
*/
function _menu_tree_data(&$links, $parents, $depth) {
$tree = array();
- while ($item = array_pop($links)) {
+ while ($menu_link = array_pop($links)) {
// We need to determine if we're on the path to root so we can later build
// the correct active trail and breadcrumb.
- $item['in_active_trail'] = in_array($item['mlid'], $parents);
+ $menu_link->in_active_trail = in_array($menu_link->id(), $parents);
// Add the current link to the tree.
- $tree[$item['mlid']] = array(
- 'link' => $item,
+ $tree[$menu_link->id()] = array(
+ 'link' => $menu_link,
'below' => array(),
);
// Look ahead to the next link, but leave it on the array so it's available
// to other recursive function calls if we return or build a sub-tree.
$next = end($links);
// Check whether the next link is the first in a new sub-tree.
- if ($next && $next['depth'] > $depth) {
+ if ($next && $next->depth > $depth) {
// Recursively call _menu_tree_data to build the sub-tree.
- $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
+ $tree[$menu_link->id()]['below'] = _menu_tree_data($links, $parents, $next->depth);
// Fetch next link after filling the sub-tree.
$next = end($links);
}
// Determine if we should exit the loop and return.
- if (!$next || $next['depth'] < $depth) {
+ if (!$next || $next->depth < $depth) {
break;
}
}
@@ -1813,7 +1785,7 @@ function menu_navigation_links($menu_name, $level = 0) {
while ($level-- > 0 && $tree) {
// Loop through the current level's items until we find one that is in trail.
while ($item = array_shift($tree)) {
- if ($item['link']['in_active_trail']) {
+ if ($item['link']->in_active_trail) {
// If the item is in the active trail, we continue in the subtree.
$tree = empty($item['below']) ? array() : $item['below'];
break;
@@ -1825,12 +1797,13 @@ function menu_navigation_links($menu_name, $level = 0) {
$router_item = menu_get_item();
$links = array();
foreach ($tree as $item) {
- if (!$item['link']['hidden']) {
+ if (!$item['link']->hidden) {
$class = '';
- $l = $item['link']['localized_options'];
- $l['href'] = $item['link']['href'];
- $l['title'] = $item['link']['title'];
- if ($item['link']['in_active_trail']) {
+ // @todo Deal with localized_options later.
+ $l = !empty($item['link']->localized_options) ? $item['link']->localized_options : $item['link']->options;
+ $l['href'] = $item['link']->href;
+ $l['title'] = $item['link']->label();
+ if ($item['link']->in_active_trail) {
$class = ' active-trail';
$l['attributes']['class'][] = 'active-trail';
}
@@ -1838,11 +1811,11 @@ function menu_navigation_links($menu_name, $level = 0) {
// sets the active class accordingly. But local tasks do not appear in
// menu trees, so if the current path is a local task, and this link is
// its tab root, then we have to set the class manually.
- if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != current_path()) {
+ if ($item['link']->href == $router_item['tab_root_href'] && $item['link']->href != current_path()) {
$l['attributes']['class'][] = 'active';
}
// Keyed with the unique mlid to generate classes in theme_links().
- $links['menu-' . $item['link']['mlid'] . $class] = $l;
+ $links['menu-' . $item['link']->id() . $class] = $l;
}
}
return $links;
@@ -2334,13 +2307,15 @@ function menu_set_active_trail($new_trail = NULL) {
}
elseif (!isset($trail)) {
$trail = array();
- $trail[] = array(
+ $trail[] = entity_create('menu_link', array(
'title' => t('Home'),
'href' => '',
'link_path' => '',
'localized_options' => array(),
- 'type' => 0,
- );
+ 'router_item' => array(
+ 'type' => 0,
+ ),
+ ));
// Try to retrieve a menu link corresponding to the current path. If more
// than one exists, the link from the most preferred menu is returned.
@@ -2352,20 +2327,25 @@ function menu_set_active_trail($new_trail = NULL) {
// Pass TRUE for $only_active_trail to make menu_tree_page_data() build
// a stripped down menu tree containing the active trail only, in case
// the given menu has not been built in this request yet.
- $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE);
+ $tree = menu_tree_page_data($preferred_link->menu_name, NULL, TRUE);
list($key, $curr) = each($tree);
}
// There is no link for the current path.
else {
- $preferred_link = $current_item;
+ $preferred_link = entity_create('menu_link', array(
+ 'title' => $current_item['title'],
+ 'href' => $current_item['href'],
+ 'options' => array(),
+ 'router_item' => $current_item,
+ ));
$curr = FALSE;
}
while ($curr) {
$link = $curr['link'];
- if ($link['in_active_trail']) {
+ if ($link->in_active_trail) {
// Add the link to the trail, unless it links to its parent.
- if (!($link['type'] & MENU_LINKS_TO_PARENT)) {
+ if (!($link->router_item['type'] & MENU_LINKS_TO_PARENT)) {
// The menu tree for the active trail may contain additional links
// that have not been translated yet, since they contain dynamic
// argument placeholders (%). Such links are not contained in regular
@@ -2373,11 +2353,11 @@ function menu_set_active_trail($new_trail = NULL) {
// translation that happens here, so as to be able to display them in
// the breadcumb for the current page.
// @see _menu_tree_check_access()
- // @see _menu_link_translate()
- if (strpos($link['href'], '%') !== FALSE) {
- _menu_link_translate($link, TRUE);
+ // @see Drupal\menu_link\MenuLink::translateCurrentPathMap()
+ if (strpos($link->href, '%') !== FALSE) {
+ $link->translateCurrentPathMap();
}
- if ($link['access']) {
+ if ($link->access()) {
$trail[] = $link;
}
}
@@ -2389,7 +2369,7 @@ function menu_set_active_trail($new_trail = NULL) {
// appending either the preferred link or the menu router item for the
// current page. Exclude it if we are on the front page.
$last = end($trail);
- if ($preferred_link && $last['href'] != $preferred_link['href'] && !drupal_is_front_page()) {
+ if ($preferred_link && $last->href != $preferred_link->href && !drupal_is_front_page()) {
$trail[] = $preferred_link;
}
}
@@ -2450,22 +2430,15 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
// Put the selected menu at the front of the list.
array_unshift($menu_names, $selected_menu);
- $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
- $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
- $query->fields('ml');
- // Weight must be taken from {menu_links}, not {menu_router}.
- $query->addField('ml', 'weight', 'link_weight');
- $query->fields('m');
- $query->condition('ml.link_path', $path_candidates, 'IN');
+ $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $path_candidates));
// Sort candidates by link path and menu name.
$candidates = array();
- foreach ($query->execute() as $candidate) {
- $candidate['weight'] = $candidate['link_weight'];
- $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
+ foreach ($menu_links as $candidate) {
+ $candidates[$candidate->link_path][$candidate->menu_name] = $candidate;
// Add any menus not already in the menu name search list.
- if (!in_array($candidate['menu_name'], $menu_names)) {
- $menu_names[] = $candidate['menu_name'];
+ if (!in_array($candidate->menu_name, $menu_names)) {
+ $menu_names[] = $candidate->menu_name;
}
}
@@ -2477,10 +2450,16 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
$candidate_item = $candidates[$link_path][$menu_name];
$map = explode('/', $path);
- _menu_translate($candidate_item, $map);
- if ($candidate_item['access']) {
+ _menu_translate($candidate_item->router_item, $map);
+ if ($candidate_item->router_item['access']) {
$preferred_links[$path][$menu_name] = $candidate_item;
if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
+ // Bring over some properties from the router item.
+ // @todo Clean-up this crap.
+ $candidate_item->href = $candidate_item->router_item['href'];
+ $candidate_item->options = $candidate_item->router_item['options'];
+ $candidate_item->localized_options = $candidate_item->router_item['localized_options'];
+
// Store the most specific link.
$preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
}
@@ -2539,7 +2518,7 @@ function menu_get_active_breadcrumb() {
// Don't show a link to the current page in the breadcrumb trail.
$end = end($active_trail);
- if ($item['href'] == $end['href']) {
+ if ($item['href'] == $end->href) {
array_pop($active_trail);
}
@@ -2561,7 +2540,7 @@ function menu_get_active_breadcrumb() {
}
foreach ($active_trail as $parent) {
- $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
+ $breadcrumb[] = l($parent->label(), $parent->href, $parent->options);
}
}
return $breadcrumb;
@@ -2574,42 +2553,10 @@ function menu_get_active_title() {
$active_trail = menu_get_active_trail();
foreach (array_reverse($active_trail) as $item) {
- if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) {
- return $item['title'];
- }
- }
-}
-
-/**
- * Gets a translated, access-checked menu link that is ready for rendering.
- *
- * This function should never be called from within node_load() or any other
- * function used as a menu object load function since an infinite recursion may
- * occur.
- *
- * @param $mlid
- * The mlid of the menu item.
- *
- * @return
- * A menu link, with $item['access'] filled and link translated for
- * rendering.
- */
-function menu_link_load($mlid) {
- if (is_numeric($mlid)) {
- $query = db_select('menu_links', 'ml');
- $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
- $query->fields('ml');
- // Weight should be taken from {menu_links}, not {menu_router}.
- $query->addField('ml', 'weight', 'link_weight');
- $query->fields('m');
- $query->condition('ml.mlid', $mlid);
- if ($item = $query->execute()->fetchAssoc()) {
- $item['weight'] = $item['link_weight'];
- _menu_link_translate($item);
- return $item;
+ if (!(bool) ($item->router_item['type'] & MENU_IS_LOCAL_TASK)) {
+ return $item->label();
}
}
- return FALSE;
}
/**
@@ -2734,156 +2681,92 @@ function menu_get_router() {
}
/**
- * Builds a link from a router item.
- */
-function _menu_link_build($item) {
- // Suggested items are disabled by default.
- if ($item['type'] == MENU_SUGGESTED_ITEM) {
- $item['hidden'] = 1;
- }
- // Hide all items that are not visible in the tree.
- elseif (!($item['type'] & MENU_VISIBLE_IN_TREE)) {
- $item['hidden'] = -1;
- }
- // Note, we set this as 'system', so that we can be sure to distinguish all
- // the menu links generated automatically from entries in {menu_router}.
- $item['module'] = 'system';
- $item += array(
- 'menu_name' => 'navigation',
- 'link_title' => $item['title'],
- 'link_path' => $item['path'],
- 'hidden' => 0,
- 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
- );
- return $item;
-}
-
-/**
* Builds menu links for the items in the menu router.
*/
function _menu_navigation_links_rebuild($menu) {
// Add normal and suggested items as links.
- $menu_links = array();
- foreach ($menu as $path => $item) {
- if ($item['_visible']) {
- $menu_links[$path] = $item;
- $sort[$path] = $item['_number_parts'];
+ $router_items = array();
+ foreach ($menu as $path => $router_item) {
+ if ($router_item['_visible']) {
+ $router_items[$path] = $router_item;
+ $sort[$path] = $router_item['_number_parts'];
}
}
- if ($menu_links) {
- // Keep an array of processed menu links, to allow menu_link_save() to
- // check this for parents instead of querying the database.
+ if ($router_items) {
+ // Keep an array of processed menu links, to allow
+ // Drupal\menu_link\MenuLinkStorageController::save() to check this for
+ // parents instead of querying the database.
$parent_candidates = array();
// Make sure no child comes before its parent.
- array_multisort($sort, SORT_NUMERIC, $menu_links);
-
- foreach ($menu_links as $key => $item) {
- $existing_item = db_select('menu_links')
- ->fields('menu_links')
- ->condition('link_path', $item['path'])
- ->condition('module', 'system')
- ->execute()->fetchAssoc();
- if ($existing_item) {
- $item['mlid'] = $existing_item['mlid'];
+ array_multisort($sort, SORT_NUMERIC, $router_items);
+
+ foreach ($router_items as $key => $router_item) {
+ if ($existing_item = entity_load_multiple_by_properties('menu_link', array('link_path' => $router_item['path'], 'module' => 'system'))) {
+ $existing_item = reset($existing_item);
+
+ $router_item['mlid'] = $existing_item->id();
// A change in hook_menu may move the link to a different menu
- if (empty($item['menu_name']) || ($item['menu_name'] == $existing_item['menu_name'])) {
- $item['menu_name'] = $existing_item['menu_name'];
- $item['plid'] = $existing_item['plid'];
+ if (empty($router_item['menu_name']) || ($router_item['menu_name'] == $existing_item->menu_name)) {
+ $router_item['menu_name'] = $existing_item->menu_name;
+ $router_item['plid'] = $existing_item->plid;
}
else {
- // It moved to a new menu. Let menu_link_save() try to find a new
- // parent based on the path.
- unset($item['plid']);
+ // It moved to a new menu.
+ // Let Drupal\menu_link\MenuLinkStorageController::save() try to find
+ // a new parent based on the path.
+ unset($router_item['plid']);
}
- $item['has_children'] = $existing_item['has_children'];
- $item['updated'] = $existing_item['updated'];
+ $router_item['has_children'] = $existing_item->has_children;
+ $router_item['updated'] = $existing_item->updated;
+ }
+ else {
+ $existing_item = NULL;
}
- if ($existing_item && $existing_item['customized']) {
- $parent_candidates[$existing_item['mlid']] = $existing_item;
+
+ if ($existing_item && $existing_item->customized) {
+ $parent_candidates[$existing_item->id()] = $existing_item;
}
else {
- $item = _menu_link_build($item);
- menu_link_save($item, $existing_item, $parent_candidates);
- $parent_candidates[$item['mlid']] = $item;
- unset($menu_links[$key]);
+ $menu_link = MenuLink::buildFromRouterItem($router_item);
+ $menu_link->original = $existing_item;
+ $menu_link->parentCandidates = $parent_candidates;
+ $menu_link->save();
+ $parent_candidates[$menu_link->id()] = $menu_link;
+ unset($router_items[$key]);
}
}
}
+
$paths = array_keys($menu);
// Updated and customized items whose router paths are gone need new ones.
- $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC))
- ->fields('menu_links', array(
- 'link_path',
- 'mlid',
- 'router_path',
- 'updated',
- ))
- ->condition(db_or()
- ->condition('updated', 1)
- ->condition(db_and()
- ->condition('router_path', $paths, 'NOT IN')
- ->condition('external', 0)
- ->condition('customized', 1)
- )
- )
- ->execute();
- foreach ($result as $item) {
- $router_path = _menu_find_router_path($item['link_path']);
- if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) {
+ $menu_links = entity_get_controller('menu_link')->loadUpdatedCustomized($paths);
+ foreach ($menu_links as $menu_link) {
+ $router_path = _menu_find_router_path($menu_link->link_path);
+ if (!empty($router_path) && ($router_path != $menu_link->router_path || $menu_link->updated)) {
// If the router path and the link path matches, it's surely a working
// item, so we clear the updated flag.
- $updated = $item['updated'] && $router_path != $item['link_path'];
- db_update('menu_links')
- ->fields(array(
- 'router_path' => $router_path,
- 'updated' => (int) $updated,
- ))
- ->condition('mlid', $item['mlid'])
- ->execute();
+ $updated = $menu_link->updated && $router_path != $menu_link->link_path;
+
+ $menu_link->router_path = $router_path;
+ $menu_link->updated = (int) $updated;
+ $menu_link->save();
}
}
+
// Find any item whose router path does not exist any more.
- $result = db_select('menu_links')
- ->fields('menu_links')
- ->condition('router_path', $paths, 'NOT IN')
- ->condition('external', 0)
- ->condition('updated', 0)
- ->condition('customized', 0)
- ->orderBy('depth', 'DESC')
- ->execute();
- // Remove all such items. Starting from those with the greatest depth will
- // minimize the amount of re-parenting done by menu_link_delete().
- foreach ($result as $item) {
- _menu_delete_item($item, TRUE);
- }
-}
+ $query = menu_link_query()
+ ->propertyCondition('router_path', $paths, 'NOT IN')
+ ->propertyCondition('external', 0)
+ ->propertyCondition('updated', 0)
+ ->propertyCondition('customized', 0)
+ ->propertyOrderBy('depth', 'DESC');
+ $result = $query->execute();
-/**
- * Clones an array of menu links.
- *
- * @param $links
- * An array of menu links to clone.
- * @param $menu_name
- * (optional) The name of a menu that the links will be cloned for. If not
- * set, the cloned links will be in the same menu as the original set of
- * links that were passed in.
- *
- * @return
- * An array of menu links with the same properties as the passed-in array,
- * but with the link identifiers removed so that a new link will be created
- * when any of them is passed in to menu_link_save().
- *
- * @see menu_link_save()
- */
-function menu_links_clone($links, $menu_name = NULL) {
- foreach ($links as &$link) {
- unset($link['mlid']);
- unset($link['plid']);
- if (isset($menu_name)) {
- $link['menu_name'] = $menu_name;
- }
+ // Remove all such items. Starting from those with the greatest depth will
+ // minimize the amount of re-parenting done by the menu link controller.
+ if (!empty($result['menu_link'])) {
+ menu_link_delete_multiple(array_keys($result['menu_link']), TRUE);
}
- return $links;
}
/**
@@ -2896,18 +2779,19 @@ function menu_links_clone($links, $menu_name = NULL) {
* An array of menu links.
*/
function menu_load_links($menu_name) {
- $links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC))
- ->fields('ml')
- ->condition('ml.menu_name', $menu_name)
+ $links = array();
+
+ $entity_query = menu_link_query()
+ ->propertyCondition('menu_name', $menu_name)
// Order by weight so as to be helpful for menus that are only one level
// deep.
- ->orderBy('weight')
- ->execute()
- ->fetchAll();
+ ->propertyOrderBy('weight');
+ $result = $entity_query->execute();
- foreach ($links as &$link) {
- $link['options'] = unserialize($link['options']);
+ if (!empty($result['menu_link'])) {
+ $links = menu_link_load_multiple(array_keys($result['menu_link']));
}
+
return $links;
}
@@ -2919,333 +2803,7 @@ function menu_load_links($menu_name) {
*/
function menu_delete_links($menu_name) {
$links = menu_load_links($menu_name);
- foreach ($links as $link) {
- // To speed up the deletion process, we reset some link properties that
- // would trigger re-parenting logic in _menu_delete_item() and
- // _menu_update_parental_status().
- $link['has_children'] = FALSE;
- $link['plid'] = 0;
- _menu_delete_item($link);
- }
-}
-
-/**
- * Delete one or several menu links.
- *
- * @param $mlid
- * A valid menu link mlid or NULL. If NULL, $path is used.
- * @param $path
- * The path to the menu items to be deleted. $mlid must be NULL.
- */
-function menu_link_delete($mlid, $path = NULL) {
- if (isset($mlid)) {
- _menu_delete_item(db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc());
- }
- else {
- $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $path));
- foreach ($result as $link) {
- _menu_delete_item($link);
- }
- }
-}
-
-/**
- * Deletes a single menu link.
- *
- * @param $item
- * Item to be deleted.
- * @param $force
- * Forces deletion. Internal use only, setting to TRUE is discouraged.
- *
- * @see menu_link_delete()
- */
-function _menu_delete_item($item, $force = FALSE) {
- $item = is_object($item) ? get_object_vars($item) : $item;
- if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) {
- // Children get re-attached to the item's parent.
- if ($item['has_children']) {
- $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = :plid", array(':plid' => $item['mlid']));
- foreach ($result as $m) {
- $child = menu_link_load($m->mlid);
- $child['plid'] = $item['plid'];
- menu_link_save($child);
- }
- }
-
- // Notify modules we are deleting the item.
- module_invoke_all('menu_link_delete', $item);
-
- db_delete('menu_links')->condition('mlid', $item['mlid'])->execute();
-
- // Update the has_children status of the parent.
- _menu_update_parental_status($item);
- menu_cache_clear($item['menu_name']);
- _menu_clear_page_cache();
- }
-}
-
-/**
- * Saves a menu link.
- *
- * After calling this function, rebuild the menu cache using
- * menu_cache_clear_all().
- *
- * @param $item
- * An associative array representing a menu link item, with elements:
- * - link_path: (required) The path of the menu item, which should be
- * normalized first by calling drupal_get_normal_path() on it.
- * - link_title: (required) Title to appear in menu for the link.
- * - menu_name: (optional) The machine name of the menu for the link.
- * Defaults to 'navigation'.
- * - weight: (optional) Integer to determine position in menu. Default is 0.
- * - expanded: (optional) Boolean that determines if the item is expanded.
- * - options: (optional) An array of options, see l() for more.
- * - mlid: (optional) Menu link identifier, the primary integer key for each
- * menu link. Can be set to an existing value, or to 0 or NULL
- * to insert a new link.
- * - plid: (optional) The mlid of the parent.
- * - router_path: (optional) The path of the relevant router item.
- * @param $existing_item
- * Optional, the current record from the {menu_links} table as an array.
- * @param $parent_candidates
- * Optional array of menu links keyed by mlid. Used by
- * _menu_navigation_links_rebuild() only.
- *
- * @return
- * The mlid of the saved menu link, or FALSE if the menu link could not be
- * saved.
- */
-function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) {
- drupal_alter('menu_link', $item);
-
- // This is the easiest way to handle the unique internal path '',
- // since a path marked as external does not need to match a router path.
- $item['external'] = (url_is_external($item['link_path']) || $item['link_path'] == '') ? 1 : 0;
- // Load defaults.
- $item += array(
- 'menu_name' => 'navigation',
- 'weight' => 0,
- 'link_title' => '',
- 'hidden' => 0,
- 'has_children' => 0,
- 'expanded' => 0,
- 'options' => array(),
- 'module' => 'menu',
- 'customized' => 0,
- 'updated' => 0,
- );
- if (isset($item['mlid'])) {
- if (!$existing_item) {
- $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc();
- }
- if ($existing_item) {
- $existing_item['options'] = unserialize($existing_item['options']);
- }
- }
- else {
- $existing_item = FALSE;
- }
-
- // Try to find a parent link. If found, assign it and derive its menu.
- $parent = _menu_link_find_parent($item, $parent_candidates);
- if (!empty($parent['mlid'])) {
- $item['plid'] = $parent['mlid'];
- $item['menu_name'] = $parent['menu_name'];
- }
- // If no corresponding parent link was found, move the link to the top-level.
- else {
- $item['plid'] = 0;
- }
- $menu_name = $item['menu_name'];
-
- if (!$existing_item) {
- $item['mlid'] = db_insert('menu_links')
- ->fields(array(
- 'menu_name' => $item['menu_name'],
- 'plid' => $item['plid'],
- 'link_path' => $item['link_path'],
- 'hidden' => $item['hidden'],
- 'external' => $item['external'],
- 'has_children' => $item['has_children'],
- 'expanded' => $item['expanded'],
- 'weight' => $item['weight'],
- 'module' => $item['module'],
- 'link_title' => $item['link_title'],
- 'options' => serialize($item['options']),
- 'customized' => $item['customized'],
- 'updated' => $item['updated'],
- ))
- ->execute();
- }
-
- // Directly fill parents for top-level links.
- if ($item['plid'] == 0) {
- $item['p1'] = $item['mlid'];
- for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
- $item["p$i"] = 0;
- }
- $item['depth'] = 1;
- }
- // Otherwise, ensure that this link's depth is not beyond the maximum depth
- // and fill parents based on the parent link.
- else {
- if ($item['has_children'] && $existing_item) {
- $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
- }
- else {
- $limit = MENU_MAX_DEPTH - 1;
- }
- if ($parent['depth'] > $limit) {
- return FALSE;
- }
- $item['depth'] = $parent['depth'] + 1;
- _menu_link_parents_set($item, $parent);
- }
- // Need to check both plid and menu_name, since plid can be 0 in any menu.
- if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
- _menu_link_move_children($item, $existing_item);
- }
- // Find the router_path.
- if (empty($item['router_path']) || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) {
- if ($item['external']) {
- $item['router_path'] = '';
- }
- else {
- // Find the router path which will serve this path.
- $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS);
- $item['router_path'] = _menu_find_router_path($item['link_path']);
- }
- }
- // If every value in $existing_item is the same in the $item, there is no
- // reason to run the update queries or clear the caches. We use
- // array_intersect_key() with the $item as the first parameter because
- // $item may have additional keys left over from building a router entry.
- // The intersect removes the extra keys, allowing a meaningful comparison.
- if (!$existing_item || (array_intersect_key($item, $existing_item) != $existing_item)) {
- db_update('menu_links')
- ->fields(array(
- 'menu_name' => $item['menu_name'],
- 'plid' => $item['plid'],
- 'link_path' => $item['link_path'],
- 'router_path' => $item['router_path'],
- 'hidden' => $item['hidden'],
- 'external' => $item['external'],
- 'has_children' => $item['has_children'],
- 'expanded' => $item['expanded'],
- 'weight' => $item['weight'],
- 'depth' => $item['depth'],
- 'p1' => $item['p1'],
- 'p2' => $item['p2'],
- 'p3' => $item['p3'],
- 'p4' => $item['p4'],
- 'p5' => $item['p5'],
- 'p6' => $item['p6'],
- 'p7' => $item['p7'],
- 'p8' => $item['p8'],
- 'p9' => $item['p9'],
- 'module' => $item['module'],
- 'link_title' => $item['link_title'],
- 'options' => serialize($item['options']),
- 'customized' => $item['customized'],
- ))
- ->condition('mlid', $item['mlid'])
- ->execute();
- // Check the has_children status of the parent.
- _menu_update_parental_status($item);
- menu_cache_clear($menu_name);
- if ($existing_item && $menu_name != $existing_item['menu_name']) {
- menu_cache_clear($existing_item['menu_name']);
- }
- // Notify modules we have acted on a menu item.
- $hook = 'menu_link_insert';
- if ($existing_item) {
- $hook = 'menu_link_update';
- }
- module_invoke_all($hook, $item);
- // Now clear the cache.
- _menu_clear_page_cache();
- }
- return $item['mlid'];
-}
-
-/**
- * Finds a possible parent for a given menu link.
- *
- * Because the parent of a given link might not exist anymore in the database,
- * we apply a set of heuristics to determine a proper parent:
- *
- * - use the passed parent link if specified and existing.
- * - else, use the first existing link down the previous link hierarchy
- * - else, for system menu links (derived from hook_menu()), reparent
- * based on the path hierarchy.
- *
- * @param $menu_link
- * A menu link.
- * @param $parent_candidates
- * An array of menu links keyed by mlid.
- *
- * @return
- * A menu link structure of the possible parent or FALSE if no valid parent
- * has been found.
- */
-function _menu_link_find_parent($menu_link, $parent_candidates = array()) {
- $parent = FALSE;
-
- // This item is explicitely top-level, skip the rest of the parenting.
- if (isset($menu_link['plid']) && empty($menu_link['plid'])) {
- return $parent;
- }
-
- // If we have a parent link ID, try to use that.
- $candidates = array();
- if (isset($menu_link['plid'])) {
- $candidates[] = $menu_link['plid'];
- }
-
- // Else, if we have a link hierarchy try to find a valid parent in there.
- if (!empty($menu_link['depth']) && $menu_link['depth'] > 1) {
- for ($depth = $menu_link['depth'] - 1; $depth >= 1; $depth--) {
- $candidates[] = $menu_link['p' . $depth];
- }
- }
-
- foreach ($candidates as $mlid) {
- if (isset($parent_candidates[$mlid])) {
- $parent = $parent_candidates[$mlid];
- }
- else {
- $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
- }
- if ($parent) {
- return $parent;
- }
- }
-
- // If everything else failed, try to derive the parent from the path
- // hierarchy. This only makes sense for links derived from menu router
- // items (ie. from hook_menu()).
- if ($menu_link['module'] == 'system') {
- $query = db_select('menu_links');
- $query->condition('module', 'system');
- // We always respect the link's 'menu_name'; inheritance for router items is
- // ensured in _menu_router_build().
- $query->condition('menu_name', $menu_link['menu_name']);
-
- // Find the parent - it must be unique.
- $parent_path = $menu_link['link_path'];
- do {
- $parent = FALSE;
- $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
- $new_query = clone $query;
- $new_query->condition('link_path', $parent_path);
- // Only valid if we get a unique result.
- if ($new_query->countQuery()->execute()->fetchField() == 1) {
- $parent = $new_query->fields('menu_links')->execute()->fetchAssoc();
- }
- } while ($parent === FALSE && $parent_path);
- }
-
- return $parent;
+ menu_link_delete_multiple(array_keys($links), FALSE, TRUE);
}
/**
@@ -3321,169 +2879,6 @@ function _menu_find_router_path($link_path) {
}
/**
- * Inserts, updates, or deletes an uncustomized menu link related to a module.
- *
- * @param $module
- * The name of the module.
- * @param $op
- * Operation to perform: insert, update or delete.
- * @param $link_path
- * The path this link points to.
- * @param $link_title
- * Title of the link to insert or new title to update the link to.
- * Unused for delete.
- *
- * @return
- * The insert op returns the mlid of the new item. Others op return NULL.
- */
-function menu_link_maintain($module, $op, $link_path, $link_title) {
- switch ($op) {
- case 'insert':
- $menu_link = array(
- 'link_title' => $link_title,
- 'link_path' => $link_path,
- 'module' => $module,
- );
- return menu_link_save($menu_link);
- break;
- case 'update':
- $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path AND module = :module AND customized = 0", array(':link_path' => $link_path, ':module' => $module))->fetchAll(PDO::FETCH_ASSOC);
- foreach ($result as $link) {
- $link['link_title'] = $link_title;
- $link['options'] = unserialize($link['options']);
- menu_link_save($link);
- }
- break;
- case 'delete':
- menu_link_delete(NULL, $link_path);
- break;
- }
-}
-
-/**
- * Finds the depth of an item's children relative to its depth.
- *
- * For example, if the item has a depth of 2, and the maximum of any child in
- * the menu link tree is 5, the relative depth is 3.
- *
- * @param $item
- * An array representing a menu link item.
- *
- * @return
- * The relative depth, or zero.
- *
- */
-function menu_link_children_relative_depth($item) {
- $query = db_select('menu_links');
- $query->addField('menu_links', 'depth');
- $query->condition('menu_name', $item['menu_name']);
- $query->orderBy('depth', 'DESC');
- $query->range(0, 1);
-
- $i = 1;
- $p = 'p1';
- while ($i <= MENU_MAX_DEPTH && $item[$p]) {
- $query->condition($p, $item[$p]);
- $p = 'p' . ++$i;
- }
-
- $max_depth = $query->execute()->fetchField();
-
- return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
-}
-
-/**
- * Updates the children of a menu link that is being moved.
- *
- * The menu name, parents (p1 - p6), and depth are updated for all children of
- * the link, and the has_children status of the previous parent is updated.
- */
-function _menu_link_move_children($item, $existing_item) {
- $query = db_update('menu_links');
-
- $query->fields(array('menu_name' => $item['menu_name']));
-
- $p = 'p1';
- $expressions = array();
- for ($i = 1; $i <= $item['depth']; $p = 'p' . ++$i) {
- $expressions[] = array($p, ":p_$i", array(":p_$i" => $item[$p]));
- }
- $j = $existing_item['depth'] + 1;
- while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
- $expressions[] = array('p' . $i++, 'p' . $j++, array());
- }
- while ($i <= MENU_MAX_DEPTH) {
- $expressions[] = array('p' . $i++, 0, array());
- }
-
- $shift = $item['depth'] - $existing_item['depth'];
- if ($shift > 0) {
- // The order of expressions must be reversed so the new values don't
- // overwrite the old ones before they can be used because "Single-table
- // UPDATE assignments are generally evaluated from left to right"
- // see: http://dev.mysql.com/doc/refman/5.0/en/update.html
- $expressions = array_reverse($expressions);
- }
- foreach ($expressions as $expression) {
- $query->expression($expression[0], $expression[1], $expression[2]);
- }
-
- $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
- $query->condition('menu_name', $existing_item['menu_name']);
- $p = 'p1';
- for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p' . ++$i) {
- $query->condition($p, $existing_item[$p]);
- }
-
- $query->execute();
-
- // Check the has_children status of the parent, while excluding this item.
- _menu_update_parental_status($existing_item, TRUE);
-}
-
-/**
- * Checks and updates the 'has_children' status for the parent of a link.
- */
-function _menu_update_parental_status($item, $exclude = FALSE) {
- // If plid == 0, there is nothing to update.
- if ($item['plid']) {
- // Check if at least one visible child exists in the table.
- $query = db_select('menu_links');
- $query->addField('menu_links', 'mlid');
- $query->condition('menu_name', $item['menu_name']);
- $query->condition('hidden', 0);
- $query->condition('plid', $item['plid']);
- $query->range(0, 1);
- if ($exclude) {
- $query->condition('mlid', $item['mlid'], '<>');
- }
- $parent_has_children = ((bool) $query->execute()->fetchField()) ? 1 : 0;
- db_update('menu_links')
- ->fields(array('has_children' => $parent_has_children))
- ->condition('mlid', $item['plid'])
- ->execute();
- }
-}
-
-/**
- * Sets the p1 through p9 values for a menu link being saved.
- */
-function _menu_link_parents_set(&$item, $parent) {
- $i = 1;
- while ($i < $item['depth']) {
- $p = 'p' . $i++;
- $item[$p] = $parent[$p];
- }
- $p = 'p' . $i++;
- // The parent (p1 - p9) corresponding to the depth always equals the mlid.
- $item[$p] = $item['mlid'];
- while ($i <= MENU_MAX_DEPTH) {
- $p = 'p' . $i++;
- $item[$p] = 0;
- }
-}
-
-/**
* Builds the router table based on the data from hook_menu().
*/
function _menu_router_build($callbacks) {
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 047026a..9b45730 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -481,7 +481,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']);
}
}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php
index ae71799..cf692c4 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php
@@ -32,7 +32,7 @@ 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();
+ $menu_link = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path));
$this->assertTrue(!empty($menu_link), 'The menu link associated with the category found in database.');
$feed = $this->createFeed();
diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc
index fbd8dda..cb99679 100644
--- a/core/modules/book/book.admin.inc
+++ b/core/modules/book/book.admin.inc
@@ -153,7 +153,7 @@ function book_admin_edit_submit($form, &$form_state) {
if ($row['plid']['#default_value'] != $values['plid'] || $row['weight']['#default_value'] != $values['weight']) {
$row['#item']['plid'] = $values['plid'];
$row['#item']['weight'] = $values['weight'];
- menu_link_save($row['#item']);
+ $row['#item']->save();
}
// Update the title if changed.
diff --git a/core/modules/book/book.info b/core/modules/book/book.info
index 1f2be6f..14fb146 100644
--- a/core/modules/book/book.info
+++ b/core/modules/book/book.info
@@ -3,6 +3,7 @@ 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
stylesheets[all][] = book.theme.css
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 7c43e28..a20f05c 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -5,6 +5,7 @@
* Allows users to create and organize related content in an outline.
*/
+use Drupal\menu_link\MenuLinkStorageController;
use Drupal\node\Node;
use Drupal\Core\Template\Attribute;
@@ -663,7 +664,7 @@ function _book_update_outline(Node $node) {
}
}
- if (menu_link_save($node->book)) {
+ if ($node->book->save()) {
if ($new) {
// Insert new.
db_insert('book')
@@ -1031,7 +1032,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);
}
/**
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php
index c3450a7..dfa81ce 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php
@@ -107,38 +107,38 @@ function testMenuNodeFormWidget() {
$this->assertNoLink($node_title);
// Add a menu link to the Management menu.
- $item = array(
+ $item = entity_create('menu_link', array(
'link_path' => 'node/' . $node->nid,
'link_title' => $this->randomName(16),
'menu_name' => 'management',
- );
- menu_link_save($item);
+ ));
+ $item->save();
// Assert that disabled Management menu is not shown on the node/$nid/edit page.
$this->drupalGet('node/' . $node->nid . '/edit');
$this->assertText('Provide a menu link', 'Link in not allowed menu not shown in node edit form');
// Assert that the link is still in the management menu after save.
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
- $link = menu_link_load($item['mlid']);
+ $link = menu_link_load($item->id());
$this->assertTrue($link, 'Link in not allowed menu still exists after saving node');
// Move the menu link back to the Navigation menu.
- $item['menu_name'] = 'navigation';
- menu_link_save($item);
+ $item->menu_name = 'navigation';
+ $item->save();
// 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);
+ 'plid' => $item->id(),
+ ));
+ $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.
- $this->assertNoOption('edit-menu-parent', 'navigation:'. $item['mlid']);
- $this->assertNoOption('edit-menu-parent', 'navigation:'. $child_item['mlid']);
+ $this->assertNoOption('edit-menu-parent', 'navigation:'. $item->id());
+ $this->assertNoOption('edit-menu-parent', 'navigation:'. $child_item->id());
// Assert that unallowed Management menu is not available in options.
$this->assertNoOption('edit-menu-parent', 'management:0');
}
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
index b4cd7e7..c0eb4c7 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
@@ -60,7 +60,7 @@ function testMenu() {
$this->drupalLogin($this->std_user);
$this->verifyAccess(403);
foreach ($this->items as $item) {
- $node = node_load(substr($item['link_path'], 5)); // Paths were set as 'node/$nid'.
+ $node = node_load(substr($item->link_path, 5)); // Paths were set as 'node/$nid'.
$this->verifyMenuLink($item, $node);
}
@@ -77,15 +77,15 @@ function testMenu() {
// Modify and reset a standard menu link.
$item = $this->getStandardMenuLink();
- $old_title = $item['link_title'];
+ $old_title = $item->link_title;
$this->modifyMenuLink($item);
- $item = menu_link_load($item['mlid']);
+ $item = menu_link_load($item->id());
// 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']);
- $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
+ $item->options['attributes']['title'] = $description;
+ $item->save();
+ $saved_item = menu_link_load($item->id());
+ $this->assertEqual($description, $saved_item->options['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
$this->resetMenuLink($item, $old_title);
}
@@ -202,7 +202,7 @@ function deleteCustomMenu($menu) {
$this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $title)), '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.');
}
@@ -220,11 +220,12 @@ function doMenuTests($menu_name = 'navigation') {
// Add menu links.
$item1 = $this->addMenuLink(0, 'node/' . $node1->nid, $menu_name);
- $item2 = $this->addMenuLink($item1['mlid'], 'node/' . $node2->nid, $menu_name, FALSE);
- $item3 = $this->addMenuLink($item2['mlid'], 'node/' . $node3->nid, $menu_name);
- $this->assertMenuLink($item1['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item1['mlid'], 'p2' => 0));
- $this->assertMenuLink($item2['mlid'], array('depth' => 2, 'has_children' => 1, 'p1' => $item1['mlid'], 'p2' => $item2['mlid'], 'p3' => 0));
- $this->assertMenuLink($item3['mlid'], array('depth' => 3, 'has_children' => 0, 'p1' => $item1['mlid'], 'p2' => $item2['mlid'], 'p3' => $item3['mlid'], 'p4' => 0));
+ $item2 = $this->addMenuLink($item1->id(), 'node/' . $node2->nid, $menu_name, FALSE);
+ $item3 = $this->addMenuLink($item2->id(), 'node/' . $node3->nid, $menu_name);
+ entity_get_controller('menu_link')->resetCache(array($item1->id(), $item2->id(), $item3->id()));
+ $this->assertMenuLink($item1->id(), array('depth' => 1, 'has_children' => 1, 'p1' => $item1->id(), 'p2' => 0));
+ $this->assertMenuLink($item2->id(), array('depth' => 2, 'has_children' => 1, 'p1' => $item1->id(), 'p2' => $item2->id(), 'p3' => 0));
+ $this->assertMenuLink($item3->id(), array('depth' => 3, 'has_children' => 0, 'p1' => $item1->id(), 'p2' => $item2->id(), 'p3' => $item3->id(), 'p4' => 0));
// Verify menu links.
$this->verifyMenuLink($item1, $node1);
@@ -233,9 +234,10 @@ function doMenuTests($menu_name = 'navigation') {
// Add more menu links.
$item4 = $this->addMenuLink(0, 'node/' . $node4->nid, $menu_name);
- $item5 = $this->addMenuLink($item4['mlid'], 'node/' . $node5->nid, $menu_name);
- $this->assertMenuLink($item4['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => 0));
- $this->assertMenuLink($item5['mlid'], array('depth' => 2, 'has_children' => 0, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => 0));
+ $item5 = $this->addMenuLink($item4->id(), 'node/' . $node5->nid, $menu_name);
+ entity_get_controller('menu_link')->resetCache(array($item4->id(), $item5->id()));
+ $this->assertMenuLink($item4->id(), array('depth' => 1, 'has_children' => 1, 'p1' => $item4->id(), 'p2' => 0));
+ $this->assertMenuLink($item5->id(), array('depth' => 2, 'has_children' => 0, 'p1' => $item4->id(), 'p2' => $item5->id(), 'p3' => 0));
// Modify menu links.
$this->modifyMenuLink($item1);
@@ -246,12 +248,13 @@ function doMenuTests($menu_name = 'navigation') {
$this->toggleMenuLink($item2);
// Move link and verify that descendants are updated.
- $this->moveMenuLink($item2, $item5['mlid'], $menu_name);
- $this->assertMenuLink($item1['mlid'], array('depth' => 1, 'has_children' => 0, 'p1' => $item1['mlid'], 'p2' => 0));
- $this->assertMenuLink($item4['mlid'], array('depth' => 1, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => 0));
- $this->assertMenuLink($item5['mlid'], array('depth' => 2, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => 0));
- $this->assertMenuLink($item2['mlid'], array('depth' => 3, 'has_children' => 1, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => $item2['mlid'], 'p4' => 0));
- $this->assertMenuLink($item3['mlid'], array('depth' => 4, 'has_children' => 0, 'p1' => $item4['mlid'], 'p2' => $item5['mlid'], 'p3' => $item2['mlid'], 'p4' => $item3['mlid'], 'p5' => 0));
+ $this->moveMenuLink($item2, $item5->id(), $menu_name);
+ entity_get_controller('menu_link')->resetCache(array($item1->id(), $item2->id(), $item3->id(), $item4->id(), $item5->id()));
+ $this->assertMenuLink($item1->id(), array('depth' => 1, 'has_children' => 0, 'p1' => $item1->id(), 'p2' => 0));
+ $this->assertMenuLink($item4->id(), array('depth' => 1, 'has_children' => 1, 'p1' => $item4->id(), 'p2' => 0));
+ $this->assertMenuLink($item5->id(), array('depth' => 2, 'has_children' => 1, 'p1' => $item4->id(), 'p2' => $item5->id(), 'p3' => 0));
+ $this->assertMenuLink($item2->id(), array('depth' => 3, 'has_children' => 1, 'p1' => $item4->id(), 'p2' => $item5->id(), 'p3' => $item2->id(), 'p4' => 0));
+ $this->assertMenuLink($item3->id(), array('depth' => 4, 'has_children' => 0, 'p1' => $item4->id(), 'p2' => $item5->id(), 'p3' => $item2->id(), 'p4' => $item3->id(), 'p5' => 0));
// Add 102 menu links with increasing weights, then make sure the last-added
// item's weight doesn't get changed because of the old hardcoded delta=50
@@ -259,7 +262,7 @@ function doMenuTests($menu_name = 'navigation') {
for ($i = -50; $i <= 51; $i++) {
$items[$i] = $this->addMenuLink(0, 'node/' . $node1->nid, $menu_name, TRUE, strval($i));
}
- $this->assertMenuLink($items[51]['mlid'], array('weight' => '51'));
+ $this->assertMenuLink($items[51]->id(), array('weight' => '51'));
// Enable a link via the overview form.
$this->disableMenuLink($item1);
@@ -267,11 +270,12 @@ function doMenuTests($menu_name = 'navigation') {
// Note in the UI the 'mlid:x[hidden]' form element maps to enabled, or
// NOT hidden.
- $edit['mlid:' . $item1['mlid'] . '[hidden]'] = TRUE;
- $this->drupalPost('admin/structure/menu/manage/' . $item1['menu_name'], $edit, t('Save configuration'));
+ $edit['mlid:' . $item1->id() . '[hidden]'] = TRUE;
+ $this->drupalPost('admin/structure/menu/manage/' . $item1->menu_name, $edit, t('Save configuration'));
+ entity_get_controller('menu_link')->resetCache(array($item1->id()));
// Verify in the database.
- $this->assertMenuLink($item1['mlid'], array('hidden' => 0));
+ $this->assertMenuLink($item1->id(), array('hidden' => 0));
// Save menu links for later tests.
$this->items[] = $item1;
@@ -288,13 +292,13 @@ function testMenuQueryAndFragment() {
$path = 'node?arg1=value1&arg2=value2';
$item = $this->addMenuLink(0, $path);
- $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit');
+ $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
$this->assertFieldByName('link_path', $path, 'Path is found with both query and fragment.');
// Now change the path to something without query and fragment.
$path = 'node';
- $this->drupalPost('admin/structure/menu/item/' . $item['mlid'] . '/edit', array('link_path' => $path), t('Save'));
- $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit');
+ $this->drupalPost('admin/structure/menu/item/' . $item->id() . '/edit', array('link_path' => $path), t('Save'));
+ $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
$this->assertFieldByName('link_path', $path, 'Path no longer has query or fragment.');
}
@@ -305,7 +309,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\MenuLink $menu_link
+ * A menu link entity.
*/
function addMenuLink($plid = 0, $link = '', $menu_name = 'navigation', $expanded = TRUE, $weight = '0') {
// View add menu link page.
@@ -326,14 +332,14 @@ function addMenuLink($plid = 0, $link = '', $menu_name = 'navigation', $e
// 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;
}
/**
@@ -368,7 +374,7 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
// Verify parent menu link.
if (isset($parent)) {
// Verify menu link.
- $title = $parent['link_title'];
+ $title = $parent->link_title;
$this->assertLink($title, 0, 'Parent menu link was displayed');
// Verify menu link link.
@@ -378,7 +384,8 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
}
// Verify menu link.
- $title = $item['link_title'];
+ $title = $item->link_title;
+ debug($title);
$this->assertLink($title, 0, 'Menu link was displayed');
// Verify menu link link.
@@ -391,7 +398,7 @@ function verifyMenuLink($item, $item_node, $parent = NULL, $parent_node = NULL)
* Change the parent of a menu link using the menu module UI.
*/
function moveMenuLink($item, $plid, $menu_name) {
- $mlid = $item['mlid'];
+ $mlid = $item->id();
$edit = array(
'parent' => $menu_name . ':' . $plid,
@@ -406,10 +413,10 @@ function moveMenuLink($item, $plid, $menu_name) {
* @param array $item Menu link passed by reference.
*/
function modifyMenuLink(&$item) {
- $item['link_title'] = $this->randomName(16);
+ $item->link_title = $this->randomName(16);
- $mlid = $item['mlid'];
- $title = $item['link_title'];
+ $mlid = $item->id();
+ $title = $item->link_title;
// Edit menu link.
$edit = array();
@@ -419,7 +426,7 @@ function modifyMenuLink(&$item) {
// Unlike most other modules, there is no confirmation message displayed.
// Verify menu link.
- $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']);
+ $this->drupalGet('admin/structure/menu/manage/' . $item->menu_name);
$this->assertText($title, 'Menu link was edited');
}
@@ -430,13 +437,14 @@ function modifyMenuLink(&$item) {
* @param string $old_title Original title for menu link.
*/
function resetMenuLink($item, $old_title) {
- $mlid = $item['mlid'];
- $title = $item['link_title'];
+ $mlid = $item->id();
+ $title = $item->link_title;
// Reset menu link.
$this->drupalPost("admin/structure/menu/item/$mlid/reset", array(), t('Reset'));
$this->assertResponse(200);
$this->assertRaw(t('The menu link was reset to its default settings.'), 'Menu link was reset');
+ entity_get_controller('menu_link')->resetCache(array($mlid));
// Verify menu link.
$this->drupalGet('');
@@ -450,8 +458,8 @@ function resetMenuLink($item, $old_title) {
* @param array $item Menu link.
*/
function deleteMenuLink($item) {
- $mlid = $item['mlid'];
- $title = $item['link_title'];
+ $mlid = $item->id();
+ $title = $item->link_title;
// Delete menu link.
$this->drupalPost("admin/structure/menu/item/$mlid/delete", array(), t('Confirm'));
@@ -474,12 +482,12 @@ function toggleMenuLink($item) {
// Verify menu link is absent.
$this->drupalGet('');
- $this->assertNoText($item['link_title'], 'Menu link was not displayed');
+ $this->assertNoText($item->link_title, 'Menu link was not displayed');
$this->enableMenuLink($item);
// Verify menu link is displayed.
$this->drupalGet('');
- $this->assertText($item['link_title'], 'Menu link was displayed');
+ $this->assertText($item->link_title, 'Menu link was displayed');
}
/**
@@ -489,9 +497,10 @@ function toggleMenuLink($item) {
* Menu link.
*/
function disableMenuLink($item) {
- $mlid = $item['mlid'];
+ $mlid = $item->id();
$edit['enabled'] = FALSE;
$this->drupalPost("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
+ entity_get_controller('menu_link')->resetCache(array($mlid));
// Unlike most other modules, there is no confirmation message displayed.
// Verify in the database.
@@ -505,9 +514,10 @@ function disableMenuLink($item) {
* Menu link.
*/
function enableMenuLink($item) {
- $mlid = $item['mlid'];
+ $mlid = $item->id();
$edit['enabled'] = TRUE;
$this->drupalPost("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
+ entity_get_controller('menu_link')->resetCache(array($mlid));
// Verify in the database.
$this->assertMenuLink($mlid, array('hidden' => 0));
@@ -524,16 +534,15 @@ 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']);
- if (!empty($options['query'])) {
- $item['link_path'] .= '?' . drupal_http_build_query($options['query']);
+ $item = menu_link_load($mlid);
+ if (!empty($item->options['query'])) {
+ $item->link_path .= '?' . drupal_http_build_query($item->options['query']);
}
- if (!empty($options['fragment'])) {
- $item['link_path'] .= '#' . $options['fragment'];
+ if (!empty($item->options['fragment'])) {
+ $item->link_path .= '#' . $item->options['fragment'];
}
foreach ($expected_item as $key => $value) {
- $this->assertEqual($item[$key], $value, format_string('Parameter %key had expected value.', array('%key' => $key)));
+ $this->assertEqual($item->{$key}, $value, format_string('Parameter %key had expected value.', array('%key' => $key)));
}
}
@@ -541,8 +550,17 @@ function assertMenuLink($mlid, array $expected_item) {
* Get standard menu link.
*/
private function getStandardMenuLink() {
+ $mlid = 0;
// 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();
+ $query = menu_link_query()
+ ->propertyCondition('module', 'system')
+ ->propertyCondition('router_path', 'user/logout');
+ $result = $query->execute();
+ if (!empty($result['menu_link'])) {
+ $link = reset($result['menu_link']);
+ $mlid = $link->entity_id;
+ }
+
$this->assertTrue($mlid > 0, 'Standard menu link id was found');
// Load menu link.
// Use api function so that link is translated for rendering.
@@ -580,7 +598,7 @@ private function verifyAccess($response = 200) {
// View menu edit node.
$item = $this->getStandardMenuLink();
- $this->drupalGet('admin/structure/menu/item/' . $item['mlid'] . '/edit');
+ $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
$this->assertResponse($response);
if ($response == 200) {
$this->assertText(t('Edit menu item'), 'Menu edit node was displayed');
diff --git a/core/modules/menu/menu.admin.inc b/core/modules/menu/menu.admin.inc
index eeb5c7f..3fa5525 100644
--- a/core/modules/menu/menu.admin.inc
+++ b/core/modules/menu/menu.admin.inc
@@ -6,6 +6,7 @@
*/
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Drupal\menu_link\MenuLink;
/**
* Menu callback which shows an overview page of all the custom menus and their descriptions.
@@ -68,16 +69,18 @@ function theme_menu_admin_overview($variables) {
function menu_overview_form($form, &$form_state, $menu) {
global $menu_admin;
$form['#attached']['css'] = array(drupal_get_path('module', 'menu') . '/menu.admin.css');
- $sql = "
- SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, m.description_callback, m.description_arguments, ml.*
- FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
- WHERE ml.menu_name = :menu
- ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
- $result = db_query($sql, array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
$links = array();
- foreach ($result as $item) {
- $links[] = $item;
+ $query = menu_link_query()
+ ->propertyCondition('menu_name', $menu['menu_name']);
+ for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
+ $query->propertyOrderBy('p' . $i, 'ASC');
}
+ $result = $query->execute();
+
+ if (!empty($result['menu_link'])) {
+ $links = menu_link_load_multiple(array_keys($result['menu_link']));
+ }
+
$delta = max(count($links), 50);
$tree = menu_tree_data($links);
$node_links = array();
@@ -117,62 +120,62 @@ function _menu_overview_tree_form($tree, $delta = 50) {
$title = '';
$item = $data['link'];
// Don't show callbacks; these have $item['hidden'] < 0.
- if ($item && $item['hidden'] >= 0) {
- $mlid = 'mlid:' . $item['mlid'];
+ if ($item && $item->hidden >= 0) {
+ $mlid = 'mlid:' . $item->id();
$form[$mlid]['#item'] = $item;
- $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled'));
- $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']);
- if ($item['hidden']) {
+ $form[$mlid]['#attributes'] = $item->hidden ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled'));
+ $form[$mlid]['title']['#markup'] = l($item->label(), $item->href, $item->options);
+ if ($item->hidden) {
$form[$mlid]['title']['#markup'] .= ' (' . t('disabled') . ')';
}
- elseif ($item['link_path'] == 'user' && $item['module'] == 'system') {
+ elseif ($item->link_path == 'user' && $item->module == 'system') {
$form[$mlid]['title']['#markup'] .= ' (' . t('logged in users only') . ')';
}
$form[$mlid]['hidden'] = array(
'#type' => 'checkbox',
- '#title' => t('Enable @title menu link', array('@title' => $item['title'])),
+ '#title' => t('Enable @title menu link', array('@title' => $item->label())),
'#title_display' => 'invisible',
- '#default_value' => !$item['hidden'],
+ '#default_value' => !$item->hidden,
);
$form[$mlid]['weight'] = array(
'#type' => 'weight',
'#delta' => $delta,
- '#default_value' => $item['weight'],
+ '#default_value' => $item->weight,
'#title_display' => 'invisible',
- '#title' => t('Weight for @title', array('@title' => $item['title'])),
+ '#title' => t('Weight for @title', array('@title' => $item->label())),
);
$form[$mlid]['mlid'] = array(
'#type' => 'hidden',
- '#value' => $item['mlid'],
+ '#value' => $item->id(),
);
$form[$mlid]['plid'] = array(
'#type' => 'hidden',
- '#default_value' => $item['plid'],
+ '#default_value' => $item->plid,
);
// Build a list of operations.
$operations = array();
$links = array();
$links['edit'] = array(
'title' => t('edit'),
- 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit',
+ 'href' => 'admin/structure/menu/item/' . $item->id() . '/edit',
);
- $operations['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'admin/structure/menu/item/' . $item['mlid'] . '/edit');
+ $operations['edit'] = array('#type' => 'link', '#title' => t('edit'), '#href' => 'admin/structure/menu/item/' . $item->id() . '/edit');
// Only items created by the menu module can be deleted.
- if ($item['module'] == 'menu' || $item['updated'] == 1) {
+ if ($item->module == 'menu' || $item->updated == 1) {
$links['delete'] = array(
'title' => t('delete'),
- 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/delete',
+ 'href' => 'admin/structure/menu/item/' . $item->id() . '/delete',
);
- $operations['delete'] = array('#type' => 'link', '#title' => t('delete'), '#href' => 'admin/structure/menu/item/' . $item['mlid'] . '/delete');
+ $operations['delete'] = array('#type' => 'link', '#title' => t('delete'), '#href' => 'admin/structure/menu/item/' . $item->id() . '/delete');
}
// Set the reset column.
- elseif ($item['module'] == 'system' && $item['customized']) {
+ elseif ($item->module == 'system' && $item->customized) {
$links['reset'] = array(
'title' => t('reset'),
- 'href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset',
+ 'href' => 'admin/structure/menu/item/' . $item->id() . '/reset',
);
- $operations['reset'] = array('#type' => 'link', '#title' => t('reset'), '#href' => 'admin/structure/menu/item/' . $item['mlid'] . '/reset');
+ $operations['reset'] = array('#type' => 'link', '#title' => t('reset'), '#href' => 'admin/structure/menu/item/' . $item->id() . '/reset');
}
$form[$mlid]['operations'] = array(
'#type' => 'operations',
@@ -213,23 +216,23 @@ function menu_overview_form_submit($form, &$form_state) {
// Update any fields that have changed in this menu item.
foreach ($fields as $field) {
if ($element[$field]['#value'] != $element[$field]['#default_value']) {
- $element['#item'][$field] = $element[$field]['#value'];
+ $element['#item']->{$field} = $element[$field]['#value'];
$updated_items[$mlid] = $element['#item'];
}
}
// Hidden is a special case, the value needs to be reversed.
if ($element['hidden']['#value'] != $element['hidden']['#default_value']) {
// Convert to integer rather than boolean due to PDO cast to string.
- $element['#item']['hidden'] = $element['hidden']['#value'] ? 0 : 1;
+ $element['#item']->hidden = $element['hidden']['#value'] ? 0 : 1;
$updated_items[$mlid] = $element['#item'];
}
}
}
// Save all our changed items to the database.
- foreach ($updated_items as $item) {
- $item['customized'] = 1;
- menu_link_save($item);
+ foreach ($updated_items as $menu_link) {
+ $menu_link->customized = 1;
+ $menu_link->save();
}
drupal_set_message(t('Your configuration has been saved.'));
}
@@ -270,7 +273,7 @@ function theme_menu_overview_form($variables) {
$element['plid']['#type'] = 'hidden';
$row = array();
- $row[] = theme('indentation', array('size' => $element['#item']['depth'] - 1)) . drupal_render($element['title']);
+ $row[] = theme('indentation', array('size' => $element['#item']->depth - 1)) . drupal_render($element['title']);
$row[] = array('data' => drupal_render($element['hidden']), 'class' => array('checkbox', 'menu-enabled'));
$row[] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']);
$row[] = drupal_render($element['operations']);
@@ -290,193 +293,6 @@ function theme_menu_overview_form($variables) {
}
/**
- * Menu callback; Build the menu link editing form.
- */
-function menu_edit_item($form, &$form_state, $type, $item, $menu) {
- if ($type == 'add' || empty($item)) {
- // This is an add form, initialize the menu link.
- $item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu['menu_name'], 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
- }
- else {
- // Get the human-readable menu title from the given menu name.
- $titles = menu_get_menus();
- $current_title = $titles[$item['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/' . $item['menu_name']);
- drupal_set_breadcrumb($breadcrumb);
- }
- $form['actions'] = array('#type' => 'actions');
- $form['link_title'] = array(
- '#type' => 'textfield',
- '#title' => t('Menu link title'),
- '#default_value' => $item['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' => $item[$key]);
- }
- // Any item created or edited via this interface is considered "customized".
- $form['customized'] = array('#type' => 'value', '#value' => 1);
- $form['original_item'] = array('#type' => 'value', '#value' => $item);
-
- $path = $item['link_path'];
- if (isset($item['options']['query'])) {
- $path .= '?' . drupal_http_build_query($item['options']['query']);
- }
- if (isset($item['options']['fragment'])) {
- $path .= '#' . $item['options']['fragment'];
- }
- if ($item['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' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
- '#required' => TRUE,
- );
- $form['actions']['delete'] = array(
- '#type' => 'submit',
- '#value' => t('Delete'),
- '#access' => $item['mlid'],
- '#submit' => array('menu_item_delete_submit'),
- '#weight' => 10,
- );
- }
- else {
- $form['_path'] = array(
- '#type' => 'item',
- '#title' => t('Path'),
- '#description' => l($item['link_title'], $item['href'], $item['options']),
- );
- }
- $form['description'] = array(
- '#type' => 'textarea',
- '#title' => t('Description'),
- '#default_value' => isset($item['options']['attributes']['title']) ? $item['options']['attributes']['title'] : '',
- '#rows' => 1,
- '#description' => t('Shown when hovering over the menu link.'),
- );
- $form['enabled'] = array(
- '#type' => 'checkbox',
- '#title' => t('Enabled'),
- '#default_value' => !$item['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' => $item['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(), $item);
- $default = $item['menu_name'] . ':' . $item['plid'];
- if (!isset($options[$default])) {
- $default = 'navigation: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.
- $sql = "SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu";
- $result = db_query($sql, array(':menu' => $item['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $row) {
- foreach ($row as $menu_item_count) {
- $delta = $menu_item_count;
- }
- }
- if ($delta < 50) {
- // Old hardcoded value.
- $delta = 50;
- }
- $form['weight'] = array(
- '#type' => 'weight',
- '#title' => t('Weight'),
- '#delta' => $delta,
- '#default_value' => $item['weight'],
- '#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'),
- );
- $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
-
- return $form;
-}
-
-/**
- * Validate form values for a menu link being added or edited.
- */
-function menu_edit_item_validate($form, &$form_state) {
- $item = &$form_state['values'];
- $normal_path = drupal_get_normal_path($item['link_path']);
- if ($item['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' => $item['link_path'], '%normal_path' => $normal_path)));
- $item['link_path'] = $normal_path;
- }
- if (!url_is_external($item['link_path'])) {
- $parsed_link = parse_url($item['link_path']);
- if (isset($parsed_link['query'])) {
- $item['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($item['options']['query']);
- }
- if (isset($parsed_link['fragment'])) {
- $item['options']['fragment'] = $parsed_link['fragment'];
- }
- else {
- unset($item['options']['fragment']);
- }
- if (isset($parsed_link['path']) && $item['link_path'] != $parsed_link['path']) {
- $item['link_path'] = $parsed_link['path'];
- }
- }
- if (!trim($item['link_path']) || !drupal_valid_path($item['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' => $item['link_path'])));
- }
-}
-
-/**
- * Submit function for the delete button on the menu item editing form.
- */
-function menu_item_delete_submit($form, &$form_state) {
- $form_state['redirect'] = 'admin/structure/menu/item/' . $form_state['values']['mlid'] . '/delete';
-}
-
-/**
- * Process menu and menu item add/edit form submissions.
- */
-function menu_edit_item_submit($form, &$form_state) {
- $item = &$form_state['values'];
-
- // The value of "hidden" is the opposite of the value
- // supplied by the "enabled" checkbox.
- $item['hidden'] = (int) !$item['enabled'];
- unset($item['enabled']);
-
- $item['options']['attributes']['title'] = $item['description'];
- list($item['menu_name'], $item['plid']) = explode(':', $item['parent']);
- if (!menu_link_save($item)) {
- drupal_set_message(t('There was an error saving the menu link.'), 'error');
- }
- else {
- drupal_set_message(t('Your configuration has been saved.'));
- }
- $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
-}
-
-/**
* Menu callback; Build the form that handles the adding/editing of a custom menu.
*/
function menu_edit_menu($form, &$form_state, $type, $menu = array()) {
@@ -567,7 +383,7 @@ function menu_delete_menu_page($menu) {
function menu_delete_menu_confirm($form, &$form_state, $menu) {
$form['#menu'] = $menu;
$caption = '';
- $num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField();
+ $num_links = entity_get_controller('menu_link')->countMenuLinks($menu['menu_name']);
if ($num_links) {
$caption .= '' . format_plural($num_links, 'Warning: There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', 'Warning: There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu['title'])) . '
';
}
@@ -589,16 +405,16 @@ function menu_delete_menu_confirm_submit($form, &$form_state) {
}
// Reset all the menu links defined by the system via hook_menu().
- $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $link) {
- menu_reset_item($link);
+ // @todo Convert this to an EFQ once we figure out 'ORDER BY m.number_parts'.
+ $result = db_query("SELECT mlid FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC))->fetchCol();
+ $menu_links = menu_link_load_multiple($result);
+ foreach ($menu_links as $link) {
+ $link->reset();
}
// Delete all links to the overview page for this menu.
- $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $link) {
- menu_link_delete($link['mlid']);
- }
+ $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'admin/structure/menu/manage/' . $menu['menu_name']));
+ menu_link_delete_multiple(array_keys($menu_links));
// Delete the custom menu and all its menu links.
menu_delete($menu);
@@ -618,7 +434,7 @@ function menu_edit_menu_name_exists($value) {
// 'menu-' is added to the menu name to avoid name-space conflicts.
$value = 'menu-' . $value;
$custom_exists = db_query_range('SELECT 1 FROM {menu_custom} WHERE menu_name = :menu', 0, 1, array(':menu' => $value))->fetchField();
- $link_exists = db_query_range("SELECT 1 FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $value))->fetchField();
+ $link_exists = menu_link_query()->propertyCondition('menu_name', $value)->range(0,1)->count()->execute();
return $custom_exists || $link_exists;
}
@@ -632,26 +448,23 @@ function menu_edit_menu_submit($form, &$form_state) {
if ($form['#insert']) {
// Add 'menu-' to the menu name to help avoid name-space conflicts.
$menu['menu_name'] = 'menu-' . $menu['menu_name'];
- $link['link_title'] = $menu['title'];
- $link['link_path'] = $path . $menu['menu_name'];
- $link['router_path'] = $path . '%';
- $link['module'] = 'menu';
- $link['plid'] = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :link AND module = :module", array(
- ':link' => 'admin/structure/menu',
- ':module' => 'system'
- ))
- ->fetchField();
-
- menu_link_save($link);
+ $system_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'admin/structure/menu', 'module' => 'system'));
+ $system_link = reset($system_link);
+ $menu_link = entity_create('menu_link', array(
+ 'link_title' => $menu['title'],
+ 'link_path' => $path . $menu['menu_name'],
+ 'router_path' => $path . '%',
+ 'plid' => $system_link->id(),
+ ));
+ $menu_link->save();
menu_save($menu);
}
else {
menu_save($menu);
- $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path", array(':path' => $path . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $m) {
- $link = menu_link_load($m['mlid']);
- $link['link_title'] = $menu['title'];
- menu_link_save($link);
+ $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $path . $menu['menu_name']));
+ foreach ($menu_links as $menu_link) {
+ $menu_link->link_title = $menu['title'];
+ $menu_link->save();
}
}
drupal_set_message(t('Your configuration has been saved.'));
@@ -659,53 +472,80 @@ function menu_edit_menu_submit($form, &$form_state) {
}
/**
- * Menu callback; Check access and present a confirm form for deleting a menu link.
+ * Menu callback: Provides the menu link submission form.
+ *
+ * @param array $menu
+ * An array representing a custom menu.
+ *
+ * @return
+ * Returns the menu link submission form.
*/
-function menu_item_delete_page($item) {
+function menu_link_add(array $menu) {
+ $menu_link = entity_create('menu_link', array(
+ 'mlid' => 0,
+ 'plid' => 0,
+ 'menu_name' => $menu['menu_name'],
+ ));
+ drupal_set_title(t('Add menu link'));
+ return entity_get_form($menu_link);
+}
+
+/**
+ * Menu callback; Check access and present a confirm form for deleting a menu
+ * link.
+ */
+function menu_link_delete_page(MenuLink $menu_link) {
// Links defined via hook_menu may not be deleted. Updated items are an
// exception, as they can be broken.
- if ($item['module'] == 'system' && !$item['updated']) {
+ if ($menu_link->module == 'system' && !$menu_link->updated) {
throw new AccessDeniedHttpException();
}
- return drupal_get_form('menu_item_delete_form', $item);
+ return drupal_get_form('menu_link_delete_form', $menu_link);
}
/**
* Build a confirm form for deletion of a single menu link.
*/
-function menu_item_delete_form($form, &$form_state, $item) {
- $form['#item'] = $item;
- return confirm_form($form, t('Are you sure you want to delete the custom menu link %item?', array('%item' => $item['link_title'])), 'admin/structure/menu/manage/' . $item['menu_name']);
+function menu_link_delete_form($form, &$form_state, MenuLink $menu_link) {
+ $form['#menu_link'] = $menu_link;
+ return confirm_form($form,
+ t('Are you sure you want to delete the custom menu link %item?', array('%item' => $menu_link->link_title)),
+ 'admin/structure/menu/manage/' . $menu_link->menu_name
+ );
}
/**
- * Process menu delete form submissions.
+ * Processes menu link delete form submissions.
*/
-function menu_item_delete_form_submit($form, &$form_state) {
- $item = $form['#item'];
- menu_link_delete($item['mlid']);
- $t_args = array('%title' => $item['link_title']);
+function menu_link_delete_form_submit($form, &$form_state) {
+ $menu_link = $form['#menu_link'];
+ menu_link_delete($menu_link->id());
+ $t_args = array('%title' => $menu_link->link_title);
drupal_set_message(t('The menu link %title has been deleted.', $t_args));
watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE);
- $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $menu_link->menu_name;
}
/**
- * Menu callback; reset a single modified menu link.
+ * Menu callback; Reset a single modified menu link.
*/
-function menu_reset_item_confirm($form, &$form_state, $item) {
- $form['item'] = array('#type' => 'value', '#value' => $item);
- return confirm_form($form, t('Are you sure you want to reset the link %item to its default values?', array('%item' => $item['link_title'])), 'admin/structure/menu/manage/' . $item['menu_name'], t('Any customizations will be lost. This action cannot be undone.'), t('Reset'));
+function menu_link_reset_form($form, &$form_state, MenuLink $menu_link) {
+ $form['#menu_link'] = $menu_link;
+ return confirm_form($form,
+ t('Are you sure you want to reset the link %item to its default values?', array('%item' => $menu_link->link_title)),
+ 'admin/structure/menu/manage/' . $menu_link->menu_name,
+ t('Any customizations will be lost. This action cannot be undone.'),
+ t('Reset'));
}
/**
- * Process menu reset item form submissions.
+ * Processes menu link reset form submissions.
*/
-function menu_reset_item_confirm_submit($form, &$form_state) {
- $item = $form_state['values']['item'];
- $new_item = menu_reset_item($item);
+function menu_link_reset_form_submit($form, &$form_state) {
+ $menu_link = $form['#menu_link'];
+ $new_menu_link = $menu_link->reset();
drupal_set_message(t('The menu link was reset to its default settings.'));
- $form_state['redirect'] = 'admin/structure/menu/manage/' . $new_item['menu_name'];
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $new_menu_link->menu_name;
}
/**
diff --git a/core/modules/menu/menu.info b/core/modules/menu/menu.info
index e5e2c8b..7aad518 100644
--- a/core/modules/menu/menu.info
+++ b/core/modules/menu/menu.info
@@ -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
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index cee4ba5..21bc7c0 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -14,6 +14,8 @@
use Drupal\node\Node;
use Symfony\Component\HttpFoundation\JsonResponse;
+use Drupal\menu_link\MenuLink;
+use Drupal\menu_link\MenuLinkStorageController;
/**
* Maximum length of menu name as entered by the user. Database length is 32
@@ -113,9 +115,9 @@ function menu_menu() {
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
$items['admin/structure/menu/manage/%menu/add'] = array(
- 'title' => 'Add link',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_item', 'add', NULL, 4),
+ 'title' => 'Add menu link',
+ 'page callback' => 'menu_link_add',
+ 'page arguments' => array(4),
'access arguments' => array('administer menu'),
'type' => MENU_LOCAL_ACTION,
'file' => 'menu.admin.inc',
@@ -138,21 +140,20 @@ function menu_menu() {
);
$items['admin/structure/menu/item/%menu_link/edit'] = array(
'title' => 'Edit menu link',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_item', 'edit', 4, NULL),
+ 'page callback' => 'entity_get_form',
+ 'page arguments' => array(4),
'access arguments' => array('administer menu'),
- 'file' => 'menu.admin.inc',
);
$items['admin/structure/menu/item/%menu_link/reset'] = array(
'title' => 'Reset menu link',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_reset_item_confirm', 4),
+ 'page arguments' => array('menu_link_reset_form', 4),
'access arguments' => array('administer menu'),
'file' => 'menu.admin.inc',
);
$items['admin/structure/menu/item/%menu_link/delete'] = array(
'title' => 'Delete menu link',
- 'page callback' => 'menu_item_delete_page',
+ 'page callback' => 'menu_link_delete_page',
'page arguments' => array(4),
'access arguments' => array('administer menu'),
'file' => 'menu.admin.inc',
@@ -183,23 +184,29 @@ function menu_theme() {
*/
function menu_enable() {
menu_router_rebuild();
- $base_link = db_query("SELECT mlid AS plid, menu_name FROM {menu_links} WHERE link_path = 'admin/structure/menu' AND module = 'system'")->fetchAssoc();
- $base_link['router_path'] = 'admin/structure/menu/manage/%';
- $base_link['module'] = 'menu';
+ $system_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'admin/structure/menu', 'module' => 'system'));
+ $system_link = reset($system_link);
+
+ $base_link = entity_create('menu_link', array(
+ 'menu_name' => $system_link->menu_name,
+ 'router_path' => 'admin/structure/menu/manage/%',
+ 'module' => 'menu',
+ ));
+
$result = db_query("SELECT * FROM {menu_custom}", array(), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $menu) {
- // $link is passed by reference to menu_link_save(), so we make a copy of $base_link.
- $link = $base_link;
- $link['mlid'] = 0;
- $link['link_title'] = $menu['title'];
- $link['link_path'] = 'admin/structure/menu/manage/' . $menu['menu_name'];
- $menu_link = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND plid = :plid", array(
- ':path' => $link['link_path'],
- ':plid' => $link['plid']
- ))
- ->fetchField();
- if (!$menu_link) {
- menu_link_save($link);
+ $link = $base_link->createDuplicate();
+ $link->plid = $system_link->id();
+ $link->link_title = $menu['title'];
+ $link->link_path = 'admin/structure/menu/manage/' . $menu['menu_name'];
+
+ $query = menu_link_query()
+ ->propertyCondition('link_path', $link->link_path)
+ ->propertyCondition('plid', $link->plid);
+ $result = $query->execute();
+
+ if (empty($result['menu_link'])) {
+ $link->save();
}
}
menu_cache_clear_all();
@@ -337,24 +344,26 @@ function menu_delete($menu) {
}
/**
- * Return a list of menu items that are valid possible parents for the given menu item.
+ * Returns a list of menu links that are valid possible parents for the given
+ * menu link.
*
- * @param $menus
+ * @param array $menus
* An array of menu names and titles, such as from menu_get_menus().
- * @param $item
- * The menu item or the node type for which to generate a list of parents.
- * If $item['mlid'] == 0 then the complete tree is returned.
- * @param $type
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * The menu link for which to generate a list of parents.
+ * If $menu_link->id() == 0 then the complete tree is returned.
+ * @param string $type
* The node type for which to generate a list of parents.
* If $item itself is a node type then $type is ignored.
- * @return
- * An array of menu link titles keyed on the a string containing the menu name
- * and mlid. The list excludes the given item and its children.
+ *
+ * @return array
+ * An array of menu link titles keyed by a string containing the menu name and
+ * mlid. The list excludes the given item and its children.
*
* @todo This has to be turned into a #process form element callback. The
* 'menu_override_parent_selector' variable is entirely superfluous.
*/
-function menu_parent_options($menus, $item, $type = '') {
+function menu_parent_options(array $menus, MenuLink $menu_link = NULL, $type = NULL) {
// The menu_links table can be practically any size and we need a way to
// allow contrib modules to provide more scalable pattern choosers.
// hook_form_alter is too late in itself because all the possible parents are
@@ -363,14 +372,12 @@ function menu_parent_options($menus, $item, $type = '') {
return array();
}
- $available_menus = array();
- if (!is_array($item)) {
- // If $item is not an array then it is a node type.
- // Use it as $type and prepare a dummy menu item for _menu_get_options().
- $type = $item;
- $item = array('mlid' => 0);
+ if (!$menu_link) {
+ $menu_link = entity_create('menu_link', array('mlid' => 0));
}
- if (empty($type)) {
+
+ $available_menus = array();
+ if (!$type) {
// If no node type is set, use all menus given to this function.
$available_menus = $menus;
}
@@ -382,7 +389,7 @@ function menu_parent_options($menus, $item, $type = '') {
}
}
- return _menu_get_options($menus, $available_menus, $item);
+ return _menu_get_options($menus, $available_menus, $menu_link);
}
/**
@@ -404,13 +411,14 @@ function menu_parent_options_js() {
/**
* Helper function to get the items of the given menu.
*/
-function _menu_get_options($menus, $available_menus, $item) {
- // If the item has children, there is an added limit to the depth of valid parents.
- if (isset($item['parent_depth_limit'])) {
- $limit = $item['parent_depth_limit'];
+function _menu_get_options($menus, $available_menus, MenuLink $menu_link) {
+ // If the menu link has children, there is an added limit to the depth of
+ // valid parents.
+ if (isset($menu_link->parent_depth_limit)) {
+ $limit = $menu_link->parent_depth_limit;
}
else {
- $limit = _menu_parent_depth_limit($item);
+ $limit = _menu_parent_depth_limit($menu_link);
}
$options = array();
@@ -418,7 +426,7 @@ function _menu_get_options($menus, $available_menus, $item) {
if (isset($available_menus[$menu_name])) {
$tree = menu_tree_all_data($menu_name, NULL);
$options[$menu_name . ':0'] = '<' . $title . '>';
- _menu_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit);
+ _menu_parents_recurse($tree, $menu_name, '--', $options, $menu_link->id(), $limit);
}
}
return $options;
@@ -429,16 +437,16 @@ function _menu_get_options($menus, $available_menus, $item) {
*/
function _menu_parents_recurse($tree, $menu_name, $indent, &$options, $exclude, $depth_limit) {
foreach ($tree as $data) {
- if ($data['link']['depth'] > $depth_limit) {
+ if ($data['link']->depth > $depth_limit) {
// Don't iterate through any links on this level.
break;
}
- if ($data['link']['mlid'] != $exclude && $data['link']['hidden'] >= 0) {
- $title = $indent . ' ' . truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
- if ($data['link']['hidden']) {
+ if ($data['link']->id() != $exclude && $data['link']->hidden >= 0) {
+ $title = $indent . ' ' . truncate_utf8($data['link']->label(), 30, TRUE, FALSE);
+ if ($data['link']->hidden) {
$title .= ' (' . t('disabled') . ')';
}
- $options[$menu_name . ':' . $data['link']['mlid']] = $title;
+ $options[$menu_name . ':' . $data['link']->id()] = $title;
if ($data['below']) {
_menu_parents_recurse($data['below'], $menu_name, $indent . '--', $options, $exclude, $depth_limit);
}
@@ -447,26 +455,6 @@ function _menu_parents_recurse($tree, $menu_name, $indent, &$options, $exclude,
}
/**
- * Reset a system-defined menu link.
- */
-function menu_reset_item($link) {
- // To reset the link to its original values, we need to retrieve its
- // definition from hook_menu(). Otherwise, for example, the link's menu would
- // not be reset, because properties like the original 'menu_name' are not
- // stored anywhere else. Since resetting a link happens rarely and this is a
- // one-time operation, retrieving the full menu router does no harm.
- $menu = menu_get_router();
- $router_item = $menu[$link['router_path']];
- $new_link = _menu_link_build($router_item);
- // Merge existing menu link's ID and 'has_children' property.
- foreach (array('mlid', 'has_children') as $key) {
- $new_link[$key] = $link[$key];
- }
- menu_link_save($new_link);
- return $new_link;
-}
-
-/**
* Implements hook_block_info().
*/
function menu_block_info() {
@@ -527,27 +515,25 @@ function menu_node_update(Node $node) {
* Helper for hook_node_insert() and hook_node_update().
*/
function menu_node_save(Node $node) {
- if (isset($node->menu)) {
- $link = &$node->menu;
- if (empty($link['enabled'])) {
- if (!empty($link['mlid'])) {
- menu_link_delete($link['mlid']);
+ if (isset($node->menu_link)) {
+ $menu_link = $node->menu_link;
+ if (empty($menu_link->enabled)) {
+ if (!$menu_link->isNew()) {
+ menu_link_delete($menu_link->id());
}
}
- elseif (trim($link['link_title'])) {
- $link['link_title'] = trim($link['link_title']);
- $link['link_path'] = "node/$node->nid";
- if (trim($link['description'])) {
- $link['options']['attributes']['title'] = trim($link['description']);
+ elseif (trim($menu_link->link_title)) {
+ $menu_link->link_title = trim($menu_link->link_title);
+ $menu_link->link_path = "node/$node->nid";
+ if (trim($menu_link->description)) {
+ $menu_link->options['attributes']['title'] = trim($menu_link->description);
}
else {
// If the description field was left empty, remove the title attribute
// from the menu link.
- unset($link['options']['attributes']['title']);
- }
- if (!menu_link_save($link)) {
- drupal_set_message(t('There was an error saving the menu link.'), 'error');
+ unset($menu_link->options['attributes']['title']);
}
+ $menu_link->save();
}
}
}
@@ -557,9 +543,13 @@ function menu_node_save(Node $node) {
*/
function menu_node_predelete(Node $node) {
// Delete all menu module links that point to this node.
- $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu'", array(':path' => 'node/' . $node->nid), array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $m) {
- menu_link_delete($m['mlid']);
+ $query = menu_link_query()
+ ->propertyCondition('link_path', 'node/' . $node->nid)
+ ->propertyCondition('module', 'menu');
+ $result = $query->execute();
+
+ if (!empty($result['menu_link'])) {
+ menu_link_delete_multiple(array_keys($result['menu_link']));
}
}
@@ -567,57 +557,63 @@ function menu_node_predelete(Node $node) {
* Implements hook_node_prepare().
*/
function menu_node_prepare(Node $node) {
- if (empty($node->menu)) {
- // Prepare the node for the edit form so that $node->menu always exists.
+ if (empty($node->menu_link)) {
+ // Prepare the node for the edit form so that $node->menu_link always exists.
$menu_name = strtok(variable_get('menu_parent_' . $node->type, 'main-menu:0'), ':');
- $item = array();
+ $menu_link = FALSE;
if (isset($node->nid)) {
$mlid = FALSE;
// Give priority to the default menu
$type_menus = variable_get('menu_options_' . $node->type, array('main-menu' => 'main-menu'));
if (in_array($menu_name, $type_menus)) {
- $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND menu_name = :menu_name AND module = 'menu' ORDER BY mlid ASC", 0, 1, array(
- ':path' => 'node/' . $node->nid,
- ':menu_name' => $menu_name,
- ))->fetchField();
+ $query = menu_link_query()
+ ->propertyCondition('link_path', 'node/' . $node->nid)
+ ->propertyCondition('menu_name', $menu_name)
+ ->propertyCondition('module', 'menu')
+ ->propertyOrderBy('mlid', 'ASC')
+ ->range(0, 1);
+ $result = $query->execute();
+
+ $mlid = (!empty($result['menu_link'])) ? key($result['menu_link']) : FALSE;
}
// Check all allowed menus if a link does not exist in the default menu.
if (!$mlid && !empty($type_menus)) {
- $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu' AND menu_name IN (:type_menus) ORDER BY mlid ASC", 0, 1, array(
- ':path' => 'node/' . $node->nid,
- ':type_menus' => array_values($type_menus),
- ))->fetchField();
+ $query = menu_link_query()
+ ->propertyCondition('link_path', 'node/' . $node->nid)
+ ->propertyCondition('menu_name', array_values($type_menus), 'IN')
+ ->propertyCondition('module', 'menu')
+ ->propertyOrderBy('mlid', 'ASC')
+ ->range(0, 1);
+ $result = $query->execute();
+
+ $mlid = (!empty($result['menu_link'])) ? key($result['menu_link']) : FALSE;
}
if ($mlid) {
- $item = menu_link_load($mlid);
+ $menu_link = menu_link_load($mlid);
}
}
+
+ if (!$menu_link) {
+ $menu_link = entity_create('menu_link', array(
+ 'mlid' => 0,
+ 'plid' => 0,
+ 'menu_name' => $menu_name,
+ ));
+ }
// Set default values.
- $node->menu = $item + array(
- 'link_title' => '',
- 'mlid' => 0,
- 'plid' => 0,
- 'menu_name' => $menu_name,
- 'weight' => 0,
- 'options' => array(),
- 'module' => 'menu',
- 'expanded' => 0,
- 'hidden' => 0,
- 'has_children' => 0,
- 'customized' => 0,
- );
+ $node->menu_link = $menu_link;
}
// Find the depth limit for the parent select.
- if (!isset($node->menu['parent_depth_limit'])) {
- $node->menu['parent_depth_limit'] = _menu_parent_depth_limit($node->menu);
+ if (!isset($node->menu_link->parent_depth_limit)) {
+ $node->menu_link->parent_depth_limit = _menu_parent_depth_limit($node->menu_link);
}
}
/**
* Find the depth limit for items in the parent select.
*/
-function _menu_parent_depth_limit($item) {
- return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? menu_link_children_relative_depth($item) : 0);
+function _menu_parent_depth_limit(MenuLink $menu_link) {
+ return MENU_MAX_DEPTH - 1 - (($menu_link->id() && $menu_link->has_children) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($menu_link) : 0);
}
/**
@@ -631,12 +627,9 @@ function menu_form_node_form_alter(&$form, $form_state) {
// Generate a list of possible parents (not including this link or descendants).
// @todo This must be handled in a #process handler.
$node = $form_state['controller']->getEntity($form_state);
- $link = $node->menu;
+ $menu_link = $node->menu_link;
$type = $node->type;
- // menu_parent_options() is goofy and can actually handle either a menu link
- // or a node type both as second argument. Pick based on whether there is
- // a link already (menu_node_prepare() sets mlid default to 0).
- $options = menu_parent_options(menu_get_menus(), $link['mlid'] ? $link : $type, $type);
+ $options = menu_parent_options(menu_get_menus(), $menu_link, $type);
// If no possible parent menu items were found, there is nothing to display.
if (empty($options)) {
return;
@@ -647,7 +640,7 @@ function menu_form_node_form_alter(&$form, $form_state) {
'#title' => t('Menu settings'),
'#access' => user_access('administer menu'),
'#collapsible' => TRUE,
- '#collapsed' => !$link['link_title'],
+ '#collapsed' => !$menu_link->link_title,
'#group' => 'additional_settings',
'#attached' => array(
'library' => array(array('menu', 'drupal.menu')),
@@ -659,7 +652,7 @@ function menu_form_node_form_alter(&$form, $form_state) {
$form['menu']['enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Provide a menu link'),
- '#default_value' => (int) (bool) $link['mlid'],
+ '#default_value' => (int) (bool) $menu_link->id(),
);
$form['menu']['link'] = array(
'#type' => 'container',
@@ -673,24 +666,24 @@ function menu_form_node_form_alter(&$form, $form_state) {
// Populate the element with the link data.
foreach (array('mlid', 'module', 'hidden', 'has_children', 'customized', 'options', 'expanded', 'hidden', 'parent_depth_limit') as $key) {
- $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $link[$key]);
+ $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $menu_link->{$key});
}
$form['menu']['link']['link_title'] = array(
'#type' => 'textfield',
'#title' => t('Menu link title'),
- '#default_value' => $link['link_title'],
+ '#default_value' => $menu_link->link_title,
);
$form['menu']['link']['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
- '#default_value' => isset($link['options']['attributes']['title']) ? $link['options']['attributes']['title'] : '',
+ '#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '',
'#rows' => 1,
'#description' => t('Shown when hovering over the menu link.'),
);
- $default = ($link['mlid'] ? $link['menu_name'] . ':' . $link['plid'] : variable_get('menu_parent_' . $type, 'main-menu:0'));
+ $default = ($menu_link->id() ? $menu_link->menu_name . ':' . $menu_link->plid : variable_get('menu_parent_' . $type, 'main-menu:0'));
// If the current parent menu item is not present in options, use the first
// available option as default value.
// @todo User should not be allowed to access menu link settings in such a
@@ -708,13 +701,7 @@ function menu_form_node_form_alter(&$form, $form_state) {
);
// Get number of items in menu so the weight selector is sized appropriately.
- $sql = "SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu";
- $result = db_query($sql, array(':menu' => $link['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $row) {
- foreach ($row as $menu_items) {
- $delta = $menu_items;
- }
- }
+ $delta = entity_get_controller('menu_link')->countMenuLinks($menu_link->menu_name);
if ($delta < 50) {
// Old hardcoded value
$delta = 50;
@@ -723,7 +710,7 @@ function menu_form_node_form_alter(&$form, $form_state) {
'#type' => 'weight',
'#title' => t('Weight'),
'#delta' => $delta,
- '#default_value' => $link['weight'],
+ '#default_value' => $menu_link->weight,
'#description' => t('Menu links with smaller weights are displayed before links with larger weights.'),
);
}
@@ -733,11 +720,12 @@ function menu_form_node_form_alter(&$form, $form_state) {
*
* @see menu_form_node_form_alter()
*/
-function menu_node_submit(Node $node, $form, $form_state) {
+function menu_node_submit(Node &$node, $form, $form_state) {
+ $node->menu_link = entity_create('menu_link', $form_state['values']['menu']);
// Decompose the selected menu parent option into 'menu_name' and 'plid', if
// the form used the default parent selection widget.
if (!empty($form_state['values']['menu']['parent'])) {
- list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
+ list($node->menu_link->menu_name, $node->menu_link->plid) = explode(':', $form_state['values']['menu']['parent']);
}
}
@@ -770,7 +758,8 @@ function menu_form_node_type_form_alter(&$form, $form_state) {
// all available menu items.
// Otherwise it is not possible to dynamically add options to the list.
// @todo Convert menu_parent_options() into a #process callback.
- $options = menu_parent_options(menu_get_menus(), array('mlid' => 0));
+ $menu_link = entity_create('menu_link', array('mlid' => 0));
+ $options = menu_parent_options(menu_get_menus(), $menu_link);
$form['menu']['menu_parent'] = array(
'#type' => 'select',
'#title' => t('Default parent item'),
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLink.php
new file mode 100644
index 0000000..0c3a81a
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLink.php
@@ -0,0 +1,355 @@
+mlid;
+ }
+
+ /**
+ * Overrides Drupal\Entity\Entity::label().
+ */
+ public function label($langcode = NULL) {
+ $label = NULL;
+
+ // Pass-through already translated labels.
+ // @see menu_set_active_trail()
+ if (isset($this->title)) {
+ return $this->title;
+ }
+
+ // Don't do anything special for external and customized links.
+ if ($this->external || $this->module != 'system' || ($this->module == 'system' && $this->customized)) {
+ $label = $this->get('link_title', $langcode);
+ }
+ // For performance, don't localize a link the user can't access.
+ elseif ($this->access()) {
+ // Quick hack until we move the localization code to the MenuLink class.
+ // @todo Remove the hack, obviously :)
+ if (!isset($this->router_item['localized'])) {
+ $this->router_item['options'] = $this->options;
+ _menu_item_localize($this->router_item, $this->map);
+ $this->localized_options = $this->router_item['localized_options'];
+ $this->description = $this->router_item['description'];
+ $this->router_item['localized'] = TRUE;
+ }
+
+ $label = $this->router_item['title'];
+ }
+
+ return $label;
+ }
+
+ /**
+ * Overrides Drupal\entity\Entity::createDuplicate().
+ */
+ public function createDuplicate() {
+ $duplicate = parent::createDuplicate();
+ $duplicate->plid = NULL;
+ return $duplicate;
+ }
+
+ /**
+ * Overrides Drupal\entity\Entity::access().
+ */
+ public function access(\Drupal\user\User $account = NULL) {
+ if ($this->external) {
+ $this->access = TRUE;
+ }
+ else {
+ // Skip links containing untranslated arguments.
+ if (strpos($this->href, '%') !== FALSE) {
+ $this->access = FALSE;
+ return FALSE;
+ }
+
+ // menu_tree_check_access() may set this ahead of time for links to nodes.
+ if (!isset($this->access)) {
+ if (!empty($this->router_item['load_functions']) && !_menu_load_objects($this->router_item, $this->map)) {
+ // An error occurred loading an object.
+ $this->access = FALSE;
+ return FALSE;
+ }
+ _menu_check_access($this->router_item, $this->map);
+ $this->access = $this->router_item['access'];
+ }
+ }
+
+ return $this->access;
+ }
+
+ /**
+ * @todo Document.
+ */
+ public function translateCurrentPathMap() {
+ // Try to derive the path argument map from the current router item,
+ // if this link's path is within the router item's path. This means
+ // that if we are on the current path 'foo/%/bar/%/baz', then
+ // menu_get_item() will have translated the menu router item for the
+ // current path, and we can take over the argument map for a link like
+ // 'foo/%/bar'. This inheritance is only valid for breadcrumb links.
+ // @see _menu_tree_check_access()
+ // @see menu_get_active_breadcrumb()
+ if (strpos($this->link_path, '%') !== FALSE && ($current_router_item = menu_get_item())) {
+ // Only translate paths within the current path.
+ if (strpos($current_router_item['path'], $this->link_path) === 0) {
+ $count = count($this->map);
+ $this->map = array_slice($current_router_item['original_map'], 0, $count);
+ $this->router_item['original_map'] = $this->map;
+ if (isset($current_router_item['map'])) {
+ $this->router_item['map'] = array_slice($current_router_item['map'], 0, $count);
+ }
+ // Reset access to check it (for the first time).
+ $this->access = NULL;
+
+ $this->href = implode('/', $this->map);
+ }
+ }
+ }
+
+
+ /**
+ * Resets a system-defined menu link.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface
+ * A menu link entity.
+ */
+ public function reset() {
+ // To reset the link to its original values, we need to retrieve its
+ // definition from hook_menu(). Otherwise, for example, the link's menu
+ // would not be reset, because properties like the original 'menu_name' are
+ // not stored anywhere else. Since resetting a link happens rarely and this
+ // is a one-time operation, retrieving the full menu router does no harm.
+ $menu = menu_get_router();
+ $router_item = $menu[$this->router_path];
+ $new_link = self::buildFromRouterItem($router_item);
+ // Merge existing menu link's ID and 'has_children' property.
+ foreach (array('mlid', 'has_children') as $key) {
+ $new_link->{$key} = $this->{$key};
+ }
+ $new_link->save();
+ return $new_link;
+ }
+
+ /**
+ * Builds a menu link entity from a router item.
+ *
+ * @param array $item
+ * A menu router item.
+ *
+ * @return MenuLink
+ * A menu link entity.
+ */
+ public static function buildFromRouterItem(array $item) {
+ // Suggested items are disabled by default.
+ if ($item['type'] == MENU_SUGGESTED_ITEM) {
+ $item['hidden'] = 1;
+ }
+ // Hide all items that are not visible in the tree.
+ elseif (!($item['type'] & MENU_VISIBLE_IN_TREE)) {
+ $item['hidden'] = -1;
+ }
+ // Note, we set this as 'system', so that we can be sure to distinguish all
+ // the menu links generated automatically from entries in {menu_router}.
+ $item['module'] = 'system';
+ $item += array(
+ 'link_title' => $item['title'],
+ 'link_path' => $item['path'],
+ 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
+ );
+ return entity_get_controller('menu_link')->create($item);
+ }
+}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
new file mode 100644
index 0000000..ff0dea4
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
@@ -0,0 +1,221 @@
+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);
+
+ $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' => '', '%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 = 'navigation: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 = entity_get_controller('menu_link')->countMenuLinks($menu_link->menu_name);
+ if ($delta < 50) {
+ // Old hardcoded value.
+ $delta = 50;
+ }
+ $form['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight'),
+ '#delta' => $delta,
+ '#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 Drupal\Core\Entity\EntityFormController::actions().
+ */
+ protected function actions(array $form, array &$form_state) {
+ $element = parent::actions($form, $form_state);
+ $element['delete']['#access'] = $this->getEntity($form_state)->module == 'menu';
+
+ return $element;
+ }
+
+ /**
+ * Overrides Drupal\Core\Entity\EntityFormController::validate().
+ */
+ public function validate(array $form, array &$form_state) {
+ $menu_link = $this->buildEntity($form, $form_state);
+
+ $normal_path = drupal_get_normal_path($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 Drupal\Core\Entity\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 Drupal\Core\Entity\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 Drupal\Core\Entity\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';
+ }
+}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
new file mode 100644
index 0000000..93072fe
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -0,0 +1,606 @@
+leftJoin('menu_router', 'm', 'base.router_path = m.path');
+ $query->fields('m', self::$routerItemFields);
+ return $query;
+ }
+
+ /**
+ * Overrides Drupal\entity\DatabaseStorageController::attachLoad().
+ */
+ protected function attachLoad(&$menu_links, $load_revision = FALSE) {
+ foreach ($menu_links as &$menu_link) {
+ $menu_link->options = unserialize($menu_link->options);
+
+ // Move all router item info to a separate $router_item property.
+ foreach (self::$routerItemFields as $field) {
+ $menu_link->router_item[$field] = $menu_link->$field;
+ unset($menu_link->$field);
+ }
+
+ // Use the weight property from the menu link.
+ $menu_link->router_item['weight'] = $menu_link->weight;
+
+ // Complete the path of the menu link with elements from the current path,
+ // if it contains dynamic placeholders (%).
+ $menu_link->map = explode('/', $menu_link->link_path);
+ if (strpos($menu_link->link_path, '%') !== FALSE) {
+ // Invoke registered to_arg callbacks.
+ if (!empty($menu_link->router_item['to_arg_functions'])) {
+ _menu_link_map_translate($menu_link->map, $menu_link->router_item['to_arg_functions']);
+ }
+ }
+ $menu_link->href = implode('/', $menu_link->map);
+ }
+
+ parent::attachLoad($menu_links, $load_revision);
+ }
+
+ /**
+ * Overrides Drupal\entity\DatabaseStorageController::save().
+ */
+ public function save(EntityInterface $entity) {
+ // We return SAVED_UPDATED by default because the logic below might not
+ // update the entity if its values haven't changed, so returning FALSE
+ // would be confusing in that situation.
+ $return = SAVED_UPDATED;
+
+ $transaction = db_transaction();
+ try {
+ // Load the stored entity, if any.
+ if (!$entity->isNew() && !isset($entity->original)) {
+ $entity->original = entity_load_unchanged($this->entityType, $entity->id());
+ }
+
+ if ($entity->isNew()) {
+ $entity->mlid = db_insert($this->entityInfo['base table'])->fields(array('menu_name' => 'navigation'))->execute();
+ $entity->enforceIsNew();
+ }
+
+ // Unlike the save() method from DatabaseStorageController, we invoke the
+ // 'presave' hook first because we want to allow modules to alter the
+ // entity before all the logic from our preSave() method.
+ $this->invokeHook('presave', $entity);
+ $this->preSave($entity);
+
+ // If every value in $entity->original is the same in the $entity, there
+ // is no reason to run the update queries or clear the caches. We use
+ // array_intersect_key() with the $entity as the first parameter because
+ // $entity may have additional keys left over from building a router entry.
+ // The intersect removes the extra keys, allowing a meaningful comparison.
+ if ($entity->isNew() || (array_intersect_key((array) $entity, (array) $entity->original) != (array) $entity->original)) {
+ $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
+
+ if ($return) {
+ if (!$entity->isNew()) {
+ $this->resetCache(array($entity->{$this->idKey}));
+ $this->postSave($entity, TRUE);
+ $this->invokeHook('update', $entity);
+ }
+ else {
+ $return = SAVED_NEW;
+ $this->resetCache();
+
+ $entity->enforceIsNew(FALSE);
+ $this->postSave($entity, FALSE);
+ $this->invokeHook('insert', $entity);
+ }
+ }
+ }
+
+ // Ignore slave server temporarily.
+ db_ignore_slave();
+ unset($entity->original);
+
+ return $return;
+ }
+ catch (Exception $e) {
+ $transaction->rollback();
+ watchdog_exception($this->entityType, $e);
+ throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Overrides Drupal\entity\DatabaseStorageController::preSave().
+ */
+ protected function preSave(EntityInterface $entity) {
+ // This is the easiest way to handle the unique internal path '',
+ // since a path marked as external does not need to match a router path.
+ $entity->external = (url_is_external($entity->link_path) || $entity->link_path == '') ? 1 : 0;
+
+ // Try to find a parent link. If found, assign it and derive its menu.
+ $parent_candidates = !empty($entity->parentCandidates) ? $entity->parentCandidates : array();
+ $parent = $this->findParent($entity, $parent_candidates);
+ if ($parent) {
+ $entity->plid = $parent->id();
+ $entity->menu_name = $parent->menu_name;
+ }
+ // If no corresponding parent link was found, move the link to the top-level.
+ else {
+ $entity->plid = 0;
+ }
+
+ // Directly fill parents for top-level links.
+ if ($entity->plid == 0) {
+ $entity->p1 = $entity->id();
+ for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
+ $entity->p{$i} = 0;
+ }
+ $entity->depth = 1;
+ }
+ // Otherwise, ensure that this link's depth is not beyond the maximum depth
+ // and fill parents based on the parent link.
+ else {
+ if ($entity->has_children && $entity->original) {
+ $limit = MENU_MAX_DEPTH - $this->findChildrenRelativeDepth($entity->original) - 1;
+ }
+ else {
+ $limit = MENU_MAX_DEPTH - 1;
+ }
+ if ($parent->depth > $limit) {
+ return FALSE;
+ }
+ $entity->depth = $parent->depth + 1;
+ $this->setParents($entity, $parent);
+ }
+
+ // Need to check both plid and menu_name, since plid can be 0 in any menu.
+ if (isset($entity->original) && ($entity->plid != $entity->original->plid || $entity->menu_name != $entity->original->menu_name)) {
+ $this->moveChildren($entity, $entity->original);
+ }
+ // Find the router_path.
+ if (empty($entity->router_path) || empty($entity->original) || (isset($entity->original) && $entity->original->link_path != $entity->link_path)) {
+ if ($entity->external) {
+ $entity->router_path = '';
+ }
+ else {
+ // Find the router path which will serve this path.
+ $entity->parts = explode('/', $entity->link_path, MENU_MAX_PARTS);
+ $entity->router_path = _menu_find_router_path($entity->link_path);
+ }
+ }
+ }
+
+ /**
+ * Overrides Drupal\entity\DatabaseStorageController::postSave().
+ */
+ function postSave(EntityInterface $entity, $update) {
+ // Check the has_children status of the parent.
+ $this->updateParentalStatus($entity);
+
+ menu_cache_clear($entity->menu_name);
+ if (isset($entity->original) && $entity->menu_name != $entity->original->menu_name) {
+ menu_cache_clear($entity->original->menu_name);
+ }
+
+ // Now clear the cache.
+ _menu_clear_page_cache();
+ }
+
+ public function deleteHelper($ids, $force = FALSE, $prevent_reparenting = FALSE) {
+ $entities = $ids ? $this->load($ids) : FALSE;
+ if (!$entities) {
+ // If no IDs or invalid IDs were passed, do nothing.
+ return;
+ }
+
+ // Activate the reparenting prevention switch.
+ $this->preventReparenting = $prevent_reparenting;
+
+ // Bail out links that can't be deleted.
+ if (!$force) {
+ foreach ($entities as $entity) {
+ if ($entity->module == 'system' || !$entity->updated) {
+ unset($ids[$entity->{$this->idKey}]);
+ }
+ }
+ }
+
+ parent::delete($ids);
+ }
+
+ /**
+ * Overrides Drupal\entity\DatabaseStorageController::preDelete().
+ */
+ protected function preDelete($entities) {
+ // Nothing to do if we don't want to reparent children.
+ if ($this->preventReparenting) {
+ return;
+ }
+
+ foreach ($entities as $entity) {
+ // Children get re-attached to the item's parent.
+ if ($entity->has_children) {
+ $children = $this->loadByProperties(array('plid' => $entity->plid));
+ foreach ($children as $child) {
+ $child->plid = $entity->plid;
+ $this->save($child);
+ }
+ }
+ }
+ }
+
+ /**
+ * Overrides Drupal\entity\DatabaseStorageController::postDelete().
+ */
+ protected function postDelete($entities) {
+ $affected_menus = array();
+ // Update the has_children status of the parent.
+ foreach ($entities as $entity) {
+ if (!$this->preventReparenting) {
+ $this->updateParentalStatus($entity);
+ }
+
+ // Store all menu names for which we need to clear the cache.
+ if (!isset($affected_menus[$entity->menu_name])) {
+ $affected_menus[$entity->menu_name] = $entity->menu_name;
+ }
+ }
+
+ foreach ($affected_menus as $menu_name) {
+ menu_cache_clear($menu_name);
+ }
+ _menu_clear_page_cache();
+ }
+
+ /**
+ * Loads updated and customized menu links for specific router paths.
+ *
+ * Note that this is a low-level method and it doesn't return fully populated
+ * menu link entities. (e.g. no fields are attached)
+ *
+ * @param array $router_paths
+ * An array of router paths.
+ *
+ * @return array
+ * An array of menu link objects indexed by their ids.
+ */
+ public function loadUpdatedCustomized(array $router_paths) {
+ $query = parent::buildQuery(NULL);
+ $query
+ ->condition(db_or()
+ ->condition('updated', 1)
+ ->condition(db_and()
+ ->condition('router_path', $router_paths, 'NOT IN')
+ ->condition('external', 0)
+ ->condition('customized', 1)
+ )
+ );
+ $query_result = $query->execute();
+
+ if (!empty($this->entityInfo['entity class'])) {
+ // We provide the necessary arguments for PDO to create objects of the
+ // specified entity class.
+ // @see Drupal\Core\Entity\EntityInterface::__construct()
+ $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
+ }
+
+ return $query_result->fetchAllAssoc($this->idKey);
+ }
+
+ /**
+ * Loads system menu link as needed by system_get_module_admin_tasks().
+ *
+ * @return array
+ * An array of menu link entities indexed by their IDs.
+ */
+ public function loadModuleAdminTasks() {
+ $query = $this->buildQuery(NULL);
+ $query
+ ->condition('base.link_path', 'admin/%', 'LIKE')
+ ->condition('base.hidden', 0, '>=')
+ ->condition('base.module', 'system')
+ ->condition('m.number_parts', 1, '>')
+ ->condition('m.page_callback', 'system_admin_menu_block_page', '<>');
+ $ids = $query->execute()->fetchCol(1);
+
+ return $this->load($ids);
+ }
+
+ /**
+ * Checks and updates the 'has_children' property for the parent of a link.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * A menu link entity.
+ */
+ protected function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
+ // If plid == 0, there is nothing to update.
+ if ($entity->plid && ($parent_entity = $this->load(array($entity->plid)))) {
+ // Check if at least one visible child exists in the table.
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', $this->entityType)
+ ->propertyCondition('menu_name', $entity->menu_name)
+ ->propertyCondition('hidden', 0)
+ ->propertyCondition('plid', $entity->plid)
+ ->count();
+
+ if ($exclude) {
+ $query->entityCondition('entity_id', $entity->id(), '<>');
+ }
+
+ $parent_has_children = ((bool) $query->execute()) ? 1 : 0;
+ $parent_entity = reset($parent_entity);
+ $parent_entity->has_children = $parent_has_children;
+ $parent_entity->save();
+
+
+// $query = db_select($this->entityInfo['base table']);
+// $query->addField($this->entityInfo['base table'], 'mlid');
+// $query->condition('menu_name', );
+// $query->condition('hidden', 0);
+// $query->condition('plid', $entity->plid);
+// $query->range(0, 1);
+// if ($exclude) {
+// $query->condition('mlid', $entity->id(), '<>');
+// }
+// $parent_has_children = ((bool) $query->execute()->fetchField()) ? 1 : 0;
+// db_update($this->entityInfo['base table'])
+// ->fields(array('has_children' => $parent_has_children))
+// ->condition('mlid', $entity->plid)
+// ->execute();
+ }
+ }
+
+ /**
+ * Finds a possible parent for a given menu link entity.
+ *
+ * Because the parent of a given link might not exist anymore in the database,
+ * we apply a set of heuristics to determine a proper parent:
+ *
+ * - use the passed parent link if specified and existing.
+ * - else, use the first existing link down the previous link hierarchy
+ * - else, for system menu links (derived from hook_menu()), reparent
+ * based on the path hierarchy.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * A menu link entity.
+ * @param array $parent_candidates
+ * An array of menu link entities keyed by mlid.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface|false
+ * A menu link entity structure of the possible parent or FALSE if no valid
+ * parent has been found.
+ */
+ protected function findParent(EntityInterface $entity, array $parent_candidates = array()) {
+ $parent = FALSE;
+
+ // This item is explicitely top-level, skip the rest of the parenting.
+ if (isset($entity->plid) && empty($entity->plid)) {
+ return $parent;
+ }
+
+ // If we have a parent link ID, try to use that.
+ $candidates = array();
+ if (isset($entity->plid)) {
+ $candidates[] = $entity->plid;
+ }
+
+ // Else, if we have a link hierarchy try to find a valid parent in there.
+ if (!empty($entity->depth) && $entity->depth > 1) {
+ for ($depth = $entity->depth - 1; $depth >= 1; $depth--) {
+ $candidates[] = $entity->{"p$depth"};
+ }
+ }
+
+ foreach ($candidates as $mlid) {
+ if (isset($parent_candidates[$mlid])) {
+ $parent = $parent_candidates[$mlid];
+ }
+ else {
+ $parent = $this->load(array($mlid));
+ $parent = reset($parent);
+ }
+ if ($parent) {
+ return $parent;
+ }
+ }
+
+ // If everything else failed, try to derive the parent from the path
+ // hierarchy. This only makes sense for links derived from menu router
+ // items (ie. from hook_menu()).
+ if ($entity->module == 'system') {
+ $query = new EntityFieldQuery();
+ $query
+ ->entityCondition('entity_type', $this->entityType)
+ ->entityCondition('entity_id', $entity->id(), '<>')
+ ->propertyCondition('module', 'system')
+ // We always respect the link's 'menu_name'; inheritance for router
+ // items is ensured in _menu_router_build().
+ ->propertyCondition('menu_name', $entity->menu_name);
+
+ // Find the parent - it must be unique.
+ $parent_path = $entity->link_path;
+ do {
+ $parent = FALSE;
+ $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
+ $new_query = clone $query;
+ $new_query->propertyCondition('link_path', $parent_path);
+ // Only valid if we get a unique result.
+ if ($new_query->count()->execute() == 1) {
+ $new_query->count = FALSE;
+ $result = $new_query->execute();
+ $parent = $this->load(array(key($result[$this->entityType])));
+ $parent = reset($parent);
+ }
+ } while ($parent === FALSE && $parent_path);
+ }
+
+ return $parent;
+ }
+
+ /**
+ * Sets the p1 through p9 properties for a menu link entity being saved.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * A menu link entity.
+ * @param \Drupal\Core\Entity\EntityInterface $parent
+ * A menu link entity.
+ */
+ protected function setParents(EntityInterface $entity, EntityInterface $parent) {
+ $i = 1;
+ while ($i < $entity->depth) {
+ $p = 'p' . $i++;
+ $entity->{$p} = $parent->{$p};
+ }
+ $p = 'p' . $i++;
+ // The parent (p1 - p9) corresponding to the depth always equals the mlid.
+ $entity->{$p} = $entity->id();
+ while ($i <= MENU_MAX_DEPTH) {
+ $p = 'p' . $i++;
+ $entity->{$p} = 0;
+ }
+ }
+
+ /**
+ * Finds the depth of an item's children relative to its depth.
+ *
+ * For example, if the item has a depth of 2 and the maximum of any child in
+ * the menu link tree is 5, the relative depth is 3.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * A menu link entity.
+ *
+ * @return int
+ * The relative depth, or zero.
+ */
+ public function findChildrenRelativeDepth(EntityInterface $entity) {
+ // @todo Since all we need is a specific field from the base table, does it
+ // make sense to convert to EFQ?
+ $query = db_select('menu_links');
+ $query->addField('menu_links', 'depth');
+ $query->condition('menu_name', $entity->menu_name);
+ $query->orderBy('depth', 'DESC');
+ $query->range(0, 1);
+
+ $i = 1;
+ $p = 'p1';
+ while ($i <= MENU_MAX_DEPTH && $entity->{$p}) {
+ $query->condition($p, $entity->{$p});
+ $p = 'p' . ++$i;
+ }
+
+ $max_depth = $query->execute()->fetchField();
+
+ return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0;
+ }
+
+ /**
+ * Updates the children of a menu link that is being moved.
+ *
+ * The menu name, parents (p1 - p6), and depth are updated for all children of
+ * the link, and the has_children status of the previous parent is updated.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * A menu link entity.
+ */
+ protected function moveChildren(EntityInterface $entity) {
+ $query = db_update($this->entityInfo['base table']);
+
+ $query->fields(array('menu_name' => $entity->menu_name));
+
+ $p = 'p1';
+ $expressions = array();
+ for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) {
+ $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}));
+ }
+ $j = $entity->original->depth + 1;
+ while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
+ $expressions[] = array('p' . $i++, 'p' . $j++, array());
+ }
+ while ($i <= MENU_MAX_DEPTH) {
+ $expressions[] = array('p' . $i++, 0, array());
+ }
+
+ $shift = $entity->depth - $entity->original->depth;
+ if ($shift > 0) {
+ // The order of expressions must be reversed so the new values don't
+ // overwrite the old ones before they can be used because "Single-table
+ // UPDATE assignments are generally evaluated from left to right"
+ // @see http://dev.mysql.com/doc/refman/5.0/en/update.html
+ $expressions = array_reverse($expressions);
+ }
+ foreach ($expressions as $expression) {
+ $query->expression($expression[0], $expression[1], $expression[2]);
+ }
+
+ $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
+ $query->condition('menu_name', $entity->original->menu_name);
+ $p = 'p1';
+ for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) {
+ $query->condition($p, $entity->original->{$p});
+ }
+
+ $query->execute();
+
+ // Check the has_children status of the parent, while excluding this item.
+ $this->updateParentalStatus($entity->original, TRUE);
+ }
+
+ /**
+ * Returns the number of menu links from a menu.
+ *
+ * @param string $menu_name
+ * The unique name of a menu.
+ */
+ public function countMenuLinks($menu_name) {
+ $entity_query = new EntityFieldQuery();
+ $entity_query
+ ->entityCondition('entity_type', $this->entityType)
+ ->propertyCondition('menu_name', $menu_name)
+ ->count();
+ return $entity_query->execute();
+ }
+}
diff --git a/core/modules/menu_link/menu_link.api.php b/core/modules/menu_link/menu_link.api.php
new file mode 100644
index 0000000..5fe0fa5
--- /dev/null
+++ b/core/modules/menu_link/menu_link.api.php
@@ -0,0 +1,138 @@
+options['alter'] has been set to a
+ * non-empty value (e.g., TRUE). This flag should be set using
+ * hook_menu_link_presave().
+ * @ todo The paragraph above is lying! This hasn't been (re)implemented yet.
+ *
+ * Implementations of this hook are able to alter any property of the menu link.
+ * For example, this hook may be used to add a page-specific query string to all
+ * menu links, or hide a certain link by setting:
+ * @code
+ * 'hidden' => 1,
+ * @endcode
+ *
+ * @param array $menu_links
+ * An array of menu link entities.
+ *
+ * @see hook_menu_link_presave()
+ */
+function hook_menu_link_load($menu_links) {
+ foreach ($menu_links as $menu_link) {
+ if ($menu_link->href == 'devel/cache/clear') {
+ $menu_link->options['query'] = drupal_get_destination();
+ }
+ }
+}
+
+
+/**
+ * Alter the data of a menu link entity before it is created or updated.
+ *
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * A menu link entity.
+ *
+ * @see hook_menu_link_load()
+ */
+function hook_menu_link_presave($menu_link) {
+ // Make all new admin links hidden (a.k.a disabled).
+ if (strpos($menu_link->link_path, 'admin') === 0 && $menu_link->isNew()) {
+ $menu_link->hidden = 1;
+ }
+ // Flag a link to be altered by hook_menu_link_load().
+ if ($menu_link->link_path == 'devel/cache/clear') {
+ $menu_link->options['alter'] = TRUE;
+ }
+ // Flag a menu link to be altered by hook_menu_link_load(), but only if it is
+ // derived from a menu router item; i.e., do not alter a custom menu link
+ // pointing to the same path that has been created by a user.
+ if ($menu_link->link_path == 'user' && $menu_link->module == 'system') {
+ $menu_link->options['alter'] = TRUE;
+ }
+}
+
+/**
+ * Inform modules that a menu link has been created.
+ *
+ * This hook is used to notify modules that menu links have been
+ * created. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * A menu link entity.
+ *
+ * @see hook_menu_link_presave()
+ * @see hook_menu_link_update()
+ * @see hook_menu_link_delete()
+ */
+function hook_menu_link_insert($menu_link) {
+ // In our sample case, we track menu items as editing sections
+ // of the site. These are stored in our table as 'disabled' items.
+ $record['mlid'] = $menu_link->id();
+ $record['menu_name'] = $menu_link->menu_name;
+ $record['status'] = 0;
+ drupal_write_record('menu_example', $record);
+}
+
+/**
+ * Inform modules that a menu link has been updated.
+ *
+ * This hook is used to notify modules that menu items have been
+ * updated. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * A menu link entity.
+ *
+ * @see hook_menu_link_presave()
+ * @see hook_menu_link_insert()
+ * @see hook_menu_link_delete()
+ */
+function hook_menu_link_update($menu_link) {
+ // If the parent menu has changed, update our record.
+ $menu_name = db_query("SELECT menu_name FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $menu_link->id()))->fetchField();
+ if ($menu_name != $menu_link->menu_name) {
+ db_update('menu_example')
+ ->fields(array('menu_name' => $menu_link->menu_name))
+ ->condition('mlid', $menu_link->id())
+ ->execute();
+ }
+}
+
+/**
+ * Inform modules that a menu link has been deleted.
+ *
+ * This hook is used to notify modules that menu links have been
+ * deleted. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * A menu link entity.
+ *
+ * @see hook_menu_link_presave()
+ * @see hook_menu_link_insert()
+ * @see hook_menu_link_update()
+ */
+function hook_menu_link_delete($menu_link) {
+ // Delete the record from our table.
+ db_delete('menu_example')
+ ->condition('mlid', $menu_link->id())
+ ->execute();
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/core/modules/menu_link/menu_link.info b/core/modules/menu_link/menu_link.info
new file mode 100644
index 0000000..c5e92d7
--- /dev/null
+++ b/core/modules/menu_link/menu_link.info
@@ -0,0 +1,8 @@
+name = Menu Link
+description = Provides menu links, trees and bunnies!
+package = Core
+version = VERSION
+core = 8.x
+; @todo Menu links functionality has been moved from system.module and menu.inc
+; to this module, so make it required until everything is moved over.
+required = TRUE
diff --git a/core/modules/menu_link/menu_link.install b/core/modules/menu_link/menu_link.install
new file mode 100644
index 0000000..fd5eabe
--- /dev/null
+++ b/core/modules/menu_link/menu_link.install
@@ -0,0 +1,213 @@
+ 'Contains the individual links within a menu.',
+ 'fields' => array(
+ 'menu_name' => array(
+ 'description' => "The menu name. All links with the same menu name (such as 'navigation') are part of the same menu.",
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'mlid' => array(
+ 'description' => 'The menu link ID (mlid) is the integer primary key.',
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'uuid' => array(
+ 'description' => 'Unique Key: Universally unique identifier for this entity.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => FALSE,
+ ),
+ 'plid' => array(
+ 'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'link_path' => array(
+ 'description' => 'The Drupal path or external path this link points to.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'router_path' => array(
+ 'description' => 'For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'langcode' => array(
+ 'description' => 'The {language}.langcode of this link.',
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'link_title' => array(
+ 'description' => 'The text displayed for the link, which may be modified by a title callback stored in {menu_router}.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'translatable' => TRUE,
+ ),
+ 'options' => array(
+ 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'translatable' => TRUE,
+ 'serialize' => TRUE,
+ ),
+ 'module' => array(
+ 'description' => 'The name of the module that generated this link.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => 'system',
+ ),
+ 'hidden' => array(
+ 'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'external' => array(
+ 'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'has_children' => array(
+ 'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'expanded' => array(
+ 'description' => '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 (1 = expanded, 0 = not expanded)',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'weight' => array(
+ 'description' => 'Link weight among links in the same menu at the same depth.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'depth' => array(
+ 'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'customized' => array(
+ 'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ 'p1' => array(
+ 'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p2' => array(
+ 'description' => 'The second mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p3' => array(
+ 'description' => 'The third mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p4' => array(
+ 'description' => 'The fourth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p5' => array(
+ 'description' => 'The fifth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p6' => array(
+ 'description' => 'The sixth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p7' => array(
+ 'description' => 'The seventh mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p8' => array(
+ 'description' => 'The eighth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'p9' => array(
+ 'description' => 'The ninth mlid in the materialized path. See p1.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'updated' => array(
+ 'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'small',
+ ),
+ ),
+ 'indexes' => array(
+ 'path_menu' => array(array('link_path', 128), 'menu_name'),
+ 'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
+ 'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+ 'router_path' => array(array('router_path', 128)),
+ ),
+ 'primary key' => array('mlid'),
+ );
+
+ return $schema;
+}
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
new file mode 100644
index 0000000..2145027
--- /dev/null
+++ b/core/modules/menu_link/menu_link.module
@@ -0,0 +1,216 @@
+ array(
+ 'label' => t('Menu link'),
+ 'entity class' => 'Drupal\menu_link\MenuLink',
+ 'controller class' => 'Drupal\menu_link\MenuLinkStorageController',
+ 'form controller class' => array(
+ 'default' => 'Drupal\menu_link\MenuLinkFormController',
+ ),
+ 'base table' => 'menu_links',
+ // @todo Investigate why this cache breaks things like drupal_get_title().
+ 'static cache' => FALSE,
+ 'uri callback' => 'menu_link_uri',
+ 'entity keys' => array(
+ 'id' => 'mlid',
+ 'label' => 'link_title',
+ 'uuid' => 'uuid',
+ ),
+ 'bundles' => array(
+ 'menu_link' => array(
+ 'label' => t('Menu link'),
+ ),
+ ),
+ ),
+ );
+
+ return $return;
+}
+
+/**
+ * Entity URI callback.
+ *
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * A menu link entity.
+ */
+function menu_link_uri(MenuLink $menu_link) {
+ return array(
+ 'path' => $menu_link->link_path,
+ );
+}
+
+/**
+ * Loads a menu link entity.
+ *
+ * This function should never be called from within node_load() or any other
+ * function used as a menu object load function since an infinite recursion may
+ * occur.
+ *
+ * @param int $mlid
+ * The menu link ID.
+ * @param bool $reset
+ * (optional) Whether to reset the menu_link_load_multiple() cache.
+ *
+ * @return Drupal\menu_link\MenuLink|false
+ * A menu link entity, with $item->access filled and link translated for
+ * rendering.
+ */
+function menu_link_load($mlid = NULL, $reset = FALSE) {
+ return entity_load('menu_link', $mlid, $reset);
+}
+
+/**
+ * Loads menu link entities from the database.
+ *
+ * @param array $mlids
+ * (optional) An array of entity IDs. If omitted, all entities are loaded.
+ * @param bool $reset
+ * (optional) Whether to reset the internal cache.
+ *
+ * @return array
+ * An array of menu link entities indexed by mlid.
+ *
+ * @see menu_link_load()
+ * @see entity_load_multiple()
+ * @see Drupal\entity\EntityFieldQuery
+ */
+function menu_link_load_multiple(array $mlids = NULL, $reset = FALSE) {
+ return entity_load_multiple('menu_link', $mlids, $reset);
+}
+
+/**
+ * Deletes a menu link.
+ *
+ * @param int $mlid
+ * A menu link ID.
+ *
+ * @see menu_link_delete_multiple()
+ */
+function menu_link_delete($mlid) {
+ menu_link_delete_multiple(array($mlid));
+}
+
+/**
+ * Deletes multiple menu links.
+ *
+ * @param array $mlids
+ * An array of menu link IDs.
+ * @param bool $force
+ * (optional) Forces deletion. Internal use only, setting to TRUE is
+ * discouraged. Defaults to FALSE.
+ * @param bool $prevent_reparenting
+ * (optional) Disables the re-parenting logic from the deletion process.
+ * Defaults to FALSE.
+ */
+function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_reparenting = FALSE) {
+ entity_get_controller('menu_link')->deleteHelper($mlids, $force, $prevent_reparenting);
+}
+
+/**
+ * Saves a menu link.
+ *
+ * After calling this function, rebuild the menu cache using
+ * menu_cache_clear_all().
+ *
+ * @param Drupal\menu_link\MenuLink $menu_link
+ * The menu link entity to be saved.
+ */
+function menu_link_save(MenuLink $menu_link) {
+ $menu_link->save();
+}
+
+/**
+ * Returns a new EntityFieldQuery object with the entity type set to menu_link.
+ *
+ * @return Drupal\Core\Entity\EntityFieldQuery
+ * A new EntityFieldQuery object.
+ *
+ * @todo Remove when we have entity_query().
+ */
+function menu_link_query() {
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', 'menu_link');
+
+ return $query;
+}
+
+/**
+ * Clones an array of menu links.
+ *
+ * @param array $links
+ * An array of menu links to clone.
+ * @param string $menu_name
+ * (optional) The name of a menu that the links will be cloned for. If not
+ * set, the cloned links will be in the same menu as the original set of
+ * links that were passed in.
+ *
+ * @return array
+ * An array of menu links with the same properties as the passed-in array,
+ * but with the link identifiers removed so that a new link will be created
+ * when any of them is passed into
+ * Drupal\menu_link\MenuLinkStorageController::save().
+ *
+ * @see Drupal\menu_link\MenuLinkStorageController::save()
+ */
+function menu_link_clone($links, $menu_name = NULL) {
+ foreach ($links as &$link) {
+ $link = $link->createDuplicate();
+ if (isset($menu_name)) {
+ $link->menu_name = $menu_name;
+ }
+ }
+ return $links;
+}
+
+/**
+ * Inserts, updates, or deletes an uncustomized menu link related to a module.
+ *
+ * @param string $module
+ * The name of the module.
+ * @param string $op
+ * Operation to perform: insert, update or delete.
+ * @param string $link_path
+ * The path this link points to.
+ * @param string $link_title
+ * Title of the link to insert or new title to update the link to.
+ * Unused for delete.
+ *
+ * @return bool|null
+ * The insert operation returns SAVED_NEW if the operation succeeded. Other
+ * operations return NULL.
+ */
+function menu_link_maintain($module, $op, $link_path, $link_title) {
+ switch ($op) {
+ case 'insert':
+ $menu_link = entity_create('menu_link', array(
+ 'link_title' => $link_title,
+ 'link_path' => $link_path,
+ 'module' => $module,)
+ );
+ return $menu_link->save();
+ break;
+ case 'update':
+ $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0));
+ foreach ($menu_links as $menu_link) {
+ $menu_link->link_title = $link_title;
+ $menu_link->save();
+ }
+ break;
+ case 'delete':
+ $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path));
+ menu_link_delete_multiple(array_keys($menu_links));
+ break;
+ }
+}
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 113cc3d..7034ef6 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -818,7 +818,7 @@ function hook_node_submit(Drupal\node\Node $node, $form, &$form_state) {
// Decompose the selected menu parent option into 'menu_name' and 'plid', if
// the form used the default parent selection widget.
if (!empty($form_state['values']['menu']['parent'])) {
- list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
+ list($node->menu_link->menu_name, $node->menu_link->plid) = explode(':', $form_state['values']['menu']['parent']);
}
}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php
index 54eeab4..ef3c05a 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php
@@ -66,7 +66,7 @@ function testShortcutQuickLink() {
theme_enable(array('seven'));
variable_set('admin_theme', 'seven');
variable_set('node_admin_theme', TRUE);
- $this->drupalGet($this->set->links[0]['link_path']);
+ $this->drupalGet($this->set->links[0]->link_path);
$this->assertRaw(t('Remove from %title shortcuts', array('%title' => $this->set->title)), '"Add to shortcuts" link properly switched to "Remove from shortcuts".');
}
@@ -79,7 +79,7 @@ function testShortcutLinkRename() {
// Attempt to rename shortcut link.
$new_link_name = $this->randomName(10);
- $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'], array('shortcut_link[link_title]' => $new_link_name, 'shortcut_link[link_path]' => $set->links[0]['link_path']), t('Save'));
+ $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]->id(), array('shortcut_link[link_title]' => $new_link_name, 'shortcut_link[link_path]' => $set->links[0]->link_path), t('Save'));
$saved_set = shortcut_set_load($set->set_name);
$titles = $this->getShortcutInformation($saved_set, 'link_title');
$this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name);
@@ -95,7 +95,7 @@ function testShortcutLinkChangePath() {
// Tests changing a shortcut path.
$new_link_path = 'admin/config';
- $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'], array('shortcut_link[link_title]' => $set->links[0]['link_title'], 'shortcut_link[link_path]' => $new_link_path), t('Save'));
+ $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]->id(), array('shortcut_link[link_title]' => $set->links[0]->link_title, 'shortcut_link[link_path]' => $new_link_path), t('Save'));
$saved_set = shortcut_set_load($set->set_name);
$paths = $this->getShortcutInformation($saved_set, 'link_path');
$this->assertTrue(in_array($new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path);
@@ -108,10 +108,10 @@ function testShortcutLinkChangePath() {
function testShortcutLinkDelete() {
$set = $this->set;
- $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'] . '/delete', array(), 'Delete');
+ $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]->id() . '/delete', array(), 'Delete');
$saved_set = shortcut_set_load($set->set_name);
$mlids = $this->getShortcutInformation($saved_set, 'mlid');
- $this->assertFalse(in_array($set->links[0]['mlid'], $mlids), 'Successfully deleted a shortcut.');
+ $this->assertFalse(in_array($set->links[0]->id(), $mlids), 'Successfully deleted a shortcut.');
}
/**
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php
index 8c94435..ddd346d 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php
@@ -52,16 +52,16 @@ function setUp() {
// Populate the default shortcut set.
$shortcut_set = shortcut_set_load(SHORTCUT_DEFAULT_SET_NAME);
- $shortcut_set->links[] = array(
+ $shortcut_set->links[] = entity_create('menu_link', array(
'link_path' => 'node/add',
'link_title' => st('Add content'),
'weight' => -20,
- );
- $shortcut_set->links[] = array(
+ ));
+ $shortcut_set->links[] = entity_create('menu_link', array(
'link_path' => 'admin/content',
'link_title' => st('Find content'),
'weight' => -19,
- );
+ ));
shortcut_set_save($shortcut_set);
}
@@ -75,6 +75,8 @@ function setUp() {
// Log in as admin and grab the default shortcut set.
$this->drupalLogin($this->admin_user);
$this->set = shortcut_set_load(SHORTCUT_DEFAULT_SET_NAME);
+ // Reset the keys of the links array.
+ $this->set->links = array_values($this->set->links);
shortcut_set_assign_user($this->set, $this->admin_user);
}
@@ -104,10 +106,10 @@ function generateShortcutSet($title = '', $default_links = TRUE, $set_name = '')
* Creates a generic shortcut link.
*/
function generateShortcutLink($path, $title = '') {
- $link = array(
+ $link = entity_create('menu_link', array(
'link_path' => $path,
'link_title' => !empty($title) ? $title : $this->randomName(10),
- );
+ ));
return $link;
}
@@ -129,7 +131,7 @@ function generateShortcutLink($path, $title = '') {
function getShortcutInformation($set, $key) {
$info = array();
foreach ($set->links as $link) {
- $info[] = $link[$key];
+ $info[] = $link->{$key};
}
return $info;
}
diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc
index e805acf..1b63fea 100644
--- a/core/modules/shortcut/shortcut.admin.inc
+++ b/core/modules/shortcut/shortcut.admin.inc
@@ -120,7 +120,7 @@ function shortcut_set_switch_submit($form, &$form_state) {
$default_set = shortcut_default_set($account);
$set = (object) array(
'title' => $form_state['values']['new'],
- 'links' => menu_links_clone($default_set->links),
+ 'links' => menu_link_clone($default_set->links),
);
shortcut_set_save($set);
$replacements = array(
@@ -241,7 +241,7 @@ function shortcut_set_add_form_submit($form, &$form_state) {
$default_set = shortcut_default_set();
$set = (object) array(
'title' => $form_state['values']['new'],
- 'links' => menu_links_clone($default_set->links),
+ 'links' => menu_link_clone($default_set->links),
);
shortcut_set_save($set);
drupal_set_message(t('The %set_name shortcut set has been created. You can edit it from this page.', array('%set_name' => $set->title)));
@@ -273,13 +273,13 @@ function shortcut_set_customize($form, &$form_state, $shortcut_set) {
);
foreach ($shortcut_set->links as $link) {
- $mlid = $link['mlid'];
- $form['shortcuts']['links'][$mlid]['name']['#markup'] = l($link['link_title'], $link['link_path']);
+ $mlid = $link->id();
+ $form['shortcuts']['links'][$mlid]['name']['#markup'] = l($link->link_title, $link->link_path);
$form['shortcuts']['links'][$mlid]['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
'#delta' => 50,
- '#default_value' => $link['weight'],
+ '#default_value' => $link->weight,
'#attributes' => array('class' => array('shortcut-weight')),
);
@@ -316,7 +316,7 @@ function shortcut_set_customize_submit($form, &$form_state) {
foreach ($form_state['values']['shortcuts']['links'] as $mlid => $data) {
$link = menu_link_load($mlid);
$link['weight'] = $data['weight'];
- menu_link_save($link);
+ $link->save();
}
drupal_set_message(t('The shortcut set has been updated.'));
}
@@ -403,7 +403,7 @@ function shortcut_link_add($form, &$form_state, $shortcut_set) {
* @see shortcut_link_edit_submit()
*/
function shortcut_link_edit($form, &$form_state, $shortcut_link) {
- drupal_set_title(t('Editing @shortcut', array('@shortcut' => $shortcut_link['link_title'])));
+ drupal_set_title(t('Editing @shortcut', array('@shortcut' => $shortcut_link->link_title)));
$form['original_shortcut_link'] = array(
'#type' => 'value',
'#value' => $shortcut_link,
@@ -424,13 +424,13 @@ function shortcut_link_edit($form, &$form_state, $shortcut_link) {
*/
function _shortcut_link_form_elements($shortcut_link = NULL) {
if (!isset($shortcut_link)) {
- $shortcut_link = array(
+ $shortcut_link = entity_create('menu_link', array(
'link_title' => '',
'link_path' => ''
- );
+ ));
}
else {
- $shortcut_link['link_path'] = ($shortcut_link['link_path'] == '') ? '' : drupal_get_path_alias($shortcut_link['link_path']);
+ $shortcut_link->link_path = ($shortcut_link->link_path == '') ? '' : drupal_get_path_alias($shortcut_link->link_path);
}
$form['shortcut_link']['#tree'] = TRUE;
@@ -439,7 +439,7 @@ function _shortcut_link_form_elements($shortcut_link = NULL) {
'#title' => t('Name'),
'#size' => 40,
'#maxlength' => 255,
- '#default_value' => $shortcut_link['link_title'],
+ '#default_value' => $shortcut_link->link_title,
'#required' => TRUE,
);
@@ -449,7 +449,7 @@ function _shortcut_link_form_elements($shortcut_link = NULL) {
'#size' => 40,
'#maxlength' => 255,
'#field_prefix' => url(NULL, array('absolute' => TRUE)),
- '#default_value' => $shortcut_link['link_path'],
+ '#default_value' => $shortcut_link->link_path,
);
$form['#validate'][] = 'shortcut_link_edit_validate';
@@ -483,11 +483,14 @@ function shortcut_link_edit_submit($form, &$form_state) {
}
$form_state['values']['shortcut_link']['link_path'] = $shortcut_path;
- $shortcut_link = array_merge($form_state['values']['original_shortcut_link'], $form_state['values']['shortcut_link']);
+ $shortcut_link = $form_state['values']['original_shortcut_link'];
+ foreach ($form_state['values']['shortcut_link'] as $key => $value) {
+ $shortcut_link->set($key, $value);
+ }
- menu_link_save($shortcut_link);
- $form_state['redirect'] = 'admin/config/user-interface/shortcut/' . $shortcut_link['menu_name'];
- drupal_set_message(t('The shortcut %link has been updated.', array('%link' => $shortcut_link['link_title'])));
+ $shortcut_link->save();
+ $form_state['redirect'] = 'admin/config/user-interface/shortcut/' . $shortcut_link->menu_name;
+ drupal_set_message(t('The shortcut %link has been updated.', array('%link' => $shortcut_link->link_title)));
}
/**
@@ -496,12 +499,12 @@ function shortcut_link_edit_submit($form, &$form_state) {
function shortcut_link_add_submit($form, &$form_state) {
// Add the shortcut link to the set.
$shortcut_set = $form_state['values']['shortcut_set'];
- $shortcut_link = $form_state['values']['shortcut_link'];
- $shortcut_link['menu_name'] = $shortcut_set->set_name;
+ $shortcut_link = entity_create('menu_link', $form_state['values']['shortcut_link']);
+ $shortcut_link->menu_name = $shortcut_set->set_name;
shortcut_admin_add_link($shortcut_link, $shortcut_set);
shortcut_set_save($shortcut_set);
- $form_state['redirect'] = 'admin/config/user-interface/shortcut/' . $shortcut_link['menu_name'];
- drupal_set_message(t('Added a shortcut for %title.', array('%title' => $shortcut_link['link_title'])));
+ $form_state['redirect'] = 'admin/config/user-interface/shortcut/' . $shortcut_link->menu_name;
+ drupal_set_message(t('Added a shortcut for %title.', array('%title' => $shortcut_link->link_title)));
}
/**
@@ -517,9 +520,9 @@ function shortcut_link_add_submit($form, &$form_state) {
*/
function shortcut_admin_add_link($shortcut_link, &$shortcut_set) {
// Normalize the path in case it is an alias.
- $shortcut_link['link_path'] = drupal_get_normal_path($shortcut_link['link_path']);
- if (empty($shortcut_link['link_path'])) {
- $shortcut_link['link_path'] = '';
+ $shortcut_link->link_path = drupal_get_normal_path($shortcut_link->link_path);
+ if (empty($shortcut_link->link_path)) {
+ $shortcut_link->link_path = '';
}
// Add the link to the end of the list.
@@ -677,8 +680,8 @@ function shortcut_link_delete($form, &$form_state, $shortcut_link) {
return confirm_form(
$form,
- t('Are you sure you want to delete the shortcut %title?', array('%title' => $shortcut_link['link_title'])),
- 'admin/config/user-interface/shortcut/' . $shortcut_link['menu_name'],
+ t('Are you sure you want to delete the shortcut %title?', array('%title' => $shortcut_link->link_title)),
+ 'admin/config/user-interface/shortcut/' . $shortcut_link->menu_name,
t('This action cannot be undone.'),
t('Delete'),
t('Cancel')
@@ -690,9 +693,9 @@ function shortcut_link_delete($form, &$form_state, $shortcut_link) {
*/
function shortcut_link_delete_submit($form, &$form_state) {
$shortcut_link = $form_state['values']['shortcut_link'];
- menu_link_delete($shortcut_link['mlid']);
- $form_state['redirect'] = 'admin/config/user-interface/shortcut/' . $shortcut_link['menu_name'];
- drupal_set_message(t('The shortcut %title has been deleted.', array('%title' => $shortcut_link['link_title'])));
+ menu_link_delete($shortcut_link->id());
+ $form_state['redirect'] = 'admin/config/user-interface/shortcut/' . $shortcut_link->menu_name;
+ drupal_set_message(t('The shortcut %title has been deleted.', array('%title' => $shortcut_link->link_title)));
}
/**
@@ -713,10 +716,10 @@ function shortcut_link_add_inline($shortcut_set) {
);
shortcut_admin_add_link($link, $shortcut_set);
if (shortcut_set_save($shortcut_set)) {
- drupal_set_message(t('Added a shortcut for %title.', array('%title' => $link['link_title'])));
+ drupal_set_message(t('Added a shortcut for %title.', array('%title' => $link->link_title)));
}
else {
- drupal_set_message(t('Unable to add a shortcut for %title.', array('%title' => $link['link_title'])));
+ drupal_set_message(t('Unable to add a shortcut for %title.', array('%title' => $link->link_title)));
}
drupal_goto();
}
diff --git a/core/modules/shortcut/shortcut.info b/core/modules/shortcut/shortcut.info
index 5ed5f2d..97f25c6 100644
--- a/core/modules/shortcut/shortcut.info
+++ b/core/modules/shortcut/shortcut.info
@@ -3,4 +3,5 @@ description = Allows users to manage customizable lists of shortcut links.
package = Core
version = VERSION
core = 8.x
+dependencies[] = menu_link
configure = admin/config/user-interface/shortcut
diff --git a/core/modules/shortcut/shortcut.install b/core/modules/shortcut/shortcut.install
index c4023d7..c2b7158 100644
--- a/core/modules/shortcut/shortcut.install
+++ b/core/modules/shortcut/shortcut.install
@@ -40,7 +40,7 @@ function shortcut_schema() {
'length' => 32,
'not null' => TRUE,
'default' => '',
- 'description' => "Primary Key: The {menu_links}.menu_name under which the set's links are stored.",
+ 'description' => "Primary Key: The menu link's menu_name under which the set's links are stored.",
),
'title' => array(
'type' => 'varchar',
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 9bbb12a..6452e07 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -293,7 +293,7 @@ function shortcut_set_switch_access($account = NULL) {
function shortcut_link_access($menu_link) {
// The link must belong to a shortcut set that the current user has access
// to edit.
- if ($shortcut_set = shortcut_set_load($menu_link['menu_name'])) {
+ if ($shortcut_set = shortcut_set_load($menu_link->menu_name)) {
return shortcut_set_edit_access($shortcut_set);
}
return FALSE;
@@ -340,13 +340,13 @@ function shortcut_set_load($set_name) {
* saved):
* - 'link_path': The Drupal path or external path that the link points to.
* - 'link_title': The title of the link.
- * Any other keys accepted by menu_link_save() may also be provided.
+ * Any other fields of a the menu link entity may also be provided.
*
* @return
* A constant which is either SAVED_NEW or SAVED_UPDATED depending on whether
* a new set was created or an existing one was updated.
*
- * @see menu_link_save()
+ * @see Drupal\menu_link\MenuLinkStorageController::save()
*/
function shortcut_set_save(&$shortcut_set) {
// First save the shortcut set itself.
@@ -366,9 +366,9 @@ function shortcut_set_save(&$shortcut_set) {
// Do not specifically associate these links with the shortcut module,
// since other modules may make them editable via the menu system.
// However, we do need to specify the correct menu name.
- $link['menu_name'] = $shortcut_set->set_name;
- $link['plid'] = 0;
- menu_link_save($link);
+ $link->menu_name = $shortcut_set->set_name;
+ $link->plid = 0;
+ $link->save();
}
// Make sure that we have a return value, since if the links were updated
// but the shortcut set was not, the call to drupal_write_record() above
@@ -428,7 +428,7 @@ function shortcut_set_delete($shortcut_set) {
function shortcut_set_reset_link_weights(&$shortcut_set) {
$weight = -50;
foreach ($shortcut_set->links as &$link) {
- $link['weight'] = $weight;
+ $link->weight = $weight;
$weight++;
}
}
@@ -563,7 +563,7 @@ function shortcut_set_get_unique_name() {
* Returns the name of a shortcut set, based on a provided number.
*
* All shortcut sets have names like "shortcut-set-N" so that they can be
- * matched with a properly-namespaced entry in the {menu_links} table.
+ * matched with properly-namespaced menu link entities.
*
* @param $number
* A number representing the shortcut set whose name should be retrieved.
@@ -675,8 +675,8 @@ function shortcut_preprocess_page(&$variables) {
// Check if $link is already a shortcut and set $link_mode accordingly.
foreach ($shortcut_set->links as $shortcut) {
- if ($link == $shortcut['link_path']) {
- $mlid = $shortcut['mlid'];
+ if ($link == $shortcut->link_path) {
+ $mlid = $shortcut->id();
break;
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
index 5c0ab5f..db13075 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/BreadcrumbTest.php
@@ -202,26 +202,26 @@ function testBreadCrumbs() {
$node2 = $this->drupalCreateNode(array(
'type' => $type,
'title' => $title,
- 'menu' => array(
+ 'menu_link' => entity_create('menu_link', array(
'enabled' => 1,
'link_title' => 'Parent ' . $title,
'description' => '',
'menu_name' => $menu,
'plid' => 0,
- ),
+ )),
));
$nid2 = $node2->nid;
$trail = $home;
$tree = array(
- "node/$nid2" => $node2->menu['link_title'],
+ "node/$nid2" => $node2->menu_link->label(),
);
$this->assertBreadcrumb("node/$nid2", $trail, $node2->title, $tree);
// The node itself should not be contained in the breadcrumb on the
// default local task, since there is no difference between both pages.
$this->assertBreadcrumb("node/$nid2/view", $trail, $node2->title, $tree);
$trail += array(
- "node/$nid2" => $node2->menu['link_title'],
+ "node/$nid2" => $node2->menu_link->label(),
);
$this->assertBreadcrumb("node/$nid2/edit", $trail);
@@ -230,13 +230,13 @@ function testBreadCrumbs() {
$node3 = $this->drupalCreateNode(array(
'type' => $type,
'title' => $title,
- 'menu' => array(
+ 'menu_link' => entity_create('menu_link', array(
'enabled' => 1,
'link_title' => 'Child ' . $title,
'description' => '',
'menu_name' => $menu,
- 'plid' => $node2->menu['mlid'],
- ),
+ 'plid' => $node2->menu_link->id(),
+ )),
));
$nid3 = $node3->nid;
@@ -245,10 +245,10 @@ function testBreadCrumbs() {
// default local task, since there is no difference between both pages.
$this->assertBreadcrumb("node/$nid3/view", $trail, $node3->title, $tree, FALSE);
$trail += array(
- "node/$nid3" => $node3->menu['link_title'],
+ "node/$nid3" => $node3->menu_link->label(),
);
$tree += array(
- "node/$nid3" => $node3->menu['link_title'],
+ "node/$nid3" => $node3->menu_link->label(),
);
$this->assertBreadcrumb("node/$nid3/edit", $trail);
@@ -270,25 +270,27 @@ function testBreadCrumbs() {
'link_path' => 'node',
);
$this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
- $link = db_query('SELECT * FROM {menu_links} WHERE link_title = :title', array(':title' => 'Root'))->fetchAssoc();
+ $link = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Root'));
+ debug($link);
+ $link = reset($link);
$edit = array(
- 'menu[parent]' => $link['menu_name'] . ':' . $link['mlid'],
+ 'menu[parent]' => $link->menu_name . ':' . $link->id(),
);
$this->drupalPost("node/{$parent->nid}/edit", $edit, t('Save'));
$expected = array(
- "node" => $link['link_title'],
+ "node" => $link->label(),
);
$trail = $home + $expected;
$tree = $expected + array(
- "node/{$parent->nid}" => $parent->menu['link_title'],
+ "node/{$parent->nid}" => $parent->menu_link->label(),
);
$this->assertBreadcrumb(NULL, $trail, $parent->title, $tree);
$trail += array(
- "node/{$parent->nid}" => $parent->menu['link_title'],
+ "node/{$parent->nid}" => $parent->menu_link->label(),
);
$tree += array(
- "node/{$child->nid}" => $child->menu['link_title'],
+ "node/{$child->nid}" => $child->menu_link->label(),
);
$this->assertBreadcrumb("node/{$child->nid}", $trail, $child->title, $tree);
@@ -328,12 +330,10 @@ function testBreadCrumbs() {
'parent' => "$menu:{$parent_mlid}",
);
$this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
- $tags[$name]['link'] = db_query('SELECT * FROM {menu_links} WHERE link_title = :title AND link_path = :href', array(
- ':title' => $edit['link_title'],
- ':href' => $edit['link_path'],
- ))->fetchAssoc();
- $tags[$name]['link']['link_path'] = $edit['link_path'];
- $parent_mlid = $tags[$name]['link']['mlid'];
+ $link = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path']));
+ $tags[$name]['link'] = reset($link);
+ $tags[$name]['link']->link_path = $edit['link_path'];
+ $parent_mlid = $tags[$name]['link']->id();
}
// Verify expected breadcrumbs for menu links.
@@ -344,9 +344,9 @@ function testBreadCrumbs() {
$link = $data['link'];
$tree += array(
- $link['link_path'] => $link['link_title'],
+ $link->link_path => $link->label(),
);
- $this->assertBreadcrumb($link['link_path'], $trail, $term->name, $tree);
+ $this->assertBreadcrumb($link->link_path, $trail, $term->name, $tree);
$this->assertRaw(check_plain($parent->title), 'Tagged node found.');
// Additionally make sure that this link appears only once; i.e., the
@@ -355,14 +355,14 @@ function testBreadCrumbs() {
// other than the breadcrumb trail.
$elements = $this->xpath('//div[@id=:menu]/descendant::a[@href=:href]', array(
':menu' => 'block-system-navigation',
- ':href' => url($link['link_path']),
+ ':href' => url($link->link_path),
));
- $this->assertTrue(count($elements) == 1, "Link to {$link['link_path']} appears only once.");
+ $this->assertTrue(count($elements) == 1, "Link to {$link->link_path} appears only once.");
// Next iteration should expect this tag as parent link.
// Note: Term name, not link name, due to taxonomy_term_page().
$trail += array(
- $link['link_path'] => $term->name,
+ $link->link_path => $term->name,
);
}
@@ -429,64 +429,60 @@ function testBreadCrumbs() {
'link_path' => 'user',
);
$this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
- $link_user = db_query('SELECT * FROM {menu_links} WHERE link_title = :title AND link_path = :href', array(
- ':title' => $edit['link_title'],
- ':href' => $edit['link_path'],
- ))->fetchAssoc();
+ $link_user = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path']));
+ $link_user = reset($link_user);
$edit = array(
'link_title' => $this->admin_user->name . ' link',
'link_path' => 'user/' . $this->admin_user->uid,
);
$this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save'));
- $link_admin_user = db_query('SELECT * FROM {menu_links} WHERE link_title = :title AND link_path = :href', array(
- ':title' => $edit['link_title'],
- ':href' => $edit['link_path'],
- ))->fetchAssoc();
+ $link_admin_user = entity_load_multiple_by_properties('menu_link', array('link_title' => $edit['link_title'], 'link_path' => $edit['link_path']));
+ $link_admin_user = reset($link_admin_user);
// Verify expected breadcrumbs for the two separate links.
$this->drupalLogout();
$trail = $home;
$tree = array(
- $link_user['link_path'] => $link_user['link_title'],
+ $link_user->link_path => $link_user->label(),
);
- $this->assertBreadcrumb('user', $trail, $link_user['link_title'], $tree);
+ $this->assertBreadcrumb('user', $trail, $link_user->label(), $tree);
$tree = array(
- $link_admin_user['link_path'] => $link_admin_user['link_title'],
+ $link_admin_user->link_path => $link_admin_user->label(),
);
- $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $link_admin_user['link_title'], $tree);
+ $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $link_admin_user->label(), $tree);
$this->drupalLogin($this->admin_user);
$trail += array(
- $link_admin_user['link_path'] => $link_admin_user['link_title'],
+ $link_admin_user->link_path => $link_admin_user->label(),
);
- $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $link_admin_user['link_title'], $tree, FALSE);
+ $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $link_admin_user->label(), $tree, FALSE);
// Move 'user/%' below 'user' and verify again.
$edit = array(
- 'parent' => "$menu:{$link_user['mlid']}",
+ 'parent' => "$menu:{$link_user->id()}",
);
- $this->drupalPost("admin/structure/menu/item/{$link_admin_user['mlid']}/edit", $edit, t('Save'));
+ $this->drupalPost("admin/structure/menu/item/{$link_admin_user->id()}/edit", $edit, t('Save'));
$this->drupalLogout();
$trail = $home;
$tree = array(
- $link_user['link_path'] => $link_user['link_title'],
+ $link_user->link_path => $link_user->label(),
);
- $this->assertBreadcrumb('user', $trail, $link_user['link_title'], $tree);
+ $this->assertBreadcrumb('user', $trail, $link_user->label(), $tree);
$trail += array(
- $link_user['link_path'] => $link_user['link_title'],
+ $link_user->link_path => $link_user->label(),
);
$tree += array(
- $link_admin_user['link_path'] => $link_admin_user['link_title'],
+ $link_admin_user->link_path => $link_admin_user->label(),
);
- $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $link_admin_user['link_title'], $tree);
+ $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $link_admin_user->label(), $tree);
$this->drupalLogin($this->admin_user);
$trail += array(
- $link_admin_user['link_path'] => $link_admin_user['link_title'],
+ $link_admin_user->link_path => $link_admin_user->label(),
);
- $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $link_admin_user['link_title'], $tree, FALSE);
+ $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $link_admin_user->label(), $tree, FALSE);
// Create an only slightly privileged user being able to access site reports
// but not administration pages.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
index 7d22246..590a728 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
@@ -26,7 +26,8 @@ public static function getInfo() {
*/
function createLinkHierarchy($module = 'menu_test') {
// First remove all the menu links.
- db_truncate('menu_links')->execute();
+ $menu_links = menu_link_load_multiple();
+ menu_link_delete_multiple(array_keys($menu_links), TRUE, TRUE);
// Then create a simple link hierarchy:
// - $parent
@@ -43,31 +44,36 @@ function createLinkHierarchy($module = 'menu_test') {
$links['parent'] = $base_options + array(
'link_path' => 'menu-test/parent',
);
- menu_link_save($links['parent']);
+ $links['parent'] = entity_create('menu_link', $links['parent']);
+ $links['parent']->save();
$links['child-1'] = $base_options + array(
'link_path' => 'menu-test/parent/child-1',
- 'plid' => $links['parent']['mlid'],
+ 'plid' => $links['parent']->id(),
);
- menu_link_save($links['child-1']);
+ $links['child-1'] = entity_create('menu_link', $links['child-1']);
+ $links['child-1']->save();
$links['child-1-1'] = $base_options + array(
'link_path' => 'menu-test/parent/child-1/child-1-1',
- 'plid' => $links['child-1']['mlid'],
+ 'plid' => $links['child-1']->id(),
);
- menu_link_save($links['child-1-1']);
+ $links['child-1-1'] = entity_create('menu_link', $links['child-1-1']);
+ $links['child-1-1']->save();
$links['child-1-2'] = $base_options + array(
'link_path' => 'menu-test/parent/child-1/child-1-2',
- 'plid' => $links['child-1']['mlid'],
+ 'plid' => $links['child-1']->id(),
);
- menu_link_save($links['child-1-2']);
+ $links['child-1-2'] = entity_create('menu_link', $links['child-1-2']);
+ $links['child-1-2']->save();
$links['child-2'] = $base_options + array(
'link_path' => 'menu-test/parent/child-2',
- 'plid' => $links['parent']['mlid'],
+ 'plid' => $links['parent']->id(),
);
- menu_link_save($links['child-2']);
+ $links['child-2'] = entity_create('menu_link', $links['child-2']);
+ $links['child-2']->save();
return $links;
}
@@ -77,12 +83,12 @@ function createLinkHierarchy($module = 'menu_test') {
*/
function assertMenuLinkParents($links, $expected_hierarchy) {
foreach ($expected_hierarchy as $child => $parent) {
- $mlid = $links[$child]['mlid'];
- $plid = $parent ? $links[$parent]['mlid'] : 0;
+ $mlid = $links[$child]->id();
+ $plid = $parent ? $links[$parent]->id() : 0;
$menu_link = menu_link_load($mlid);
- menu_link_save($menu_link);
- $this->assertEqual($menu_link['plid'], $plid, format_string('Menu link %mlid has parent of %plid, expected %expected_plid.', array('%mlid' => $mlid, '%plid' => $menu_link['plid'], '%expected_plid' => $plid)));
+ $menu_link->save();
+ $this->assertEqual($menu_link->plid, $plid, format_string('Menu link %mlid has parent of %plid, expected %expected_plid.', array('%mlid' => $mlid, '%plid' => $menu_link->plid, '%expected_plid' => $plid)));
}
}
@@ -105,8 +111,8 @@ function testMenuLinkReparenting($module = 'menu_test') {
// Start over, and move child-1 under child-2, and check that all the
// childs of child-1 have been moved too.
$links = $this->createLinkHierarchy($module);
- $links['child-1']['plid'] = $links['child-2']['mlid'];
- menu_link_save($links['child-1']);
+ $links['child-1']->plid = $links['child-2']->id();
+ $links['child-1']->save();
$expected_hierarchy = array(
'parent' => FALSE,
@@ -123,7 +129,7 @@ function testMenuLinkReparenting($module = 'menu_test') {
// test in that case.
if ($module != 'system') {
$links = $this->createLinkHierarchy($module);
- menu_link_delete($links['child-1']['mlid']);
+ menu_link_delete($links['child-1']->id());
$expected_hierarchy = array(
'parent' => FALSE,
@@ -141,7 +147,7 @@ function testMenuLinkReparenting($module = 'menu_test') {
$links = $this->createLinkHierarchy($module);
// Don't do that at home.
db_delete('menu_links')
- ->condition('mlid', $links['child-1']['mlid'])
+ ->condition('mlid', $links['child-1']->id())
->execute();
$expected_hierarchy = array(
@@ -157,10 +163,11 @@ function testMenuLinkReparenting($module = 'menu_test') {
$links = $this->createLinkHierarchy($module);
// Don't do that at home.
db_delete('menu_links')
- ->condition('mlid', $links['parent']['mlid'])
+ ->condition('mlid', $links['parent']->id())
->execute();
$expected_hierarchy = array(
+ 'child-1' => FALSE,
'child-1-1' => 'child-1',
'child-1-2' => 'child-1',
'child-2' => FALSE,
@@ -181,8 +188,8 @@ function testMenuLinkRouterReparenting() {
// Move child-1-2 has a child of child-2, making the link hierarchy
// inconsistent with the path hierarchy.
- $links['child-1-2']['plid'] = $links['child-2']['mlid'];
- menu_link_save($links['child-1-2']);
+ $links['child-1-2']->plid = $links['child-2']->id();
+ $links['child-1-2']->save();
// Check the new hierarchy.
$expected_hierarchy = array(
@@ -199,8 +206,9 @@ function testMenuLinkRouterReparenting() {
// top-level.
// Don't do that at home.
db_delete('menu_links')
- ->condition('mlid', $links['parent']['mlid'])
+ ->condition('mlid', $links['parent']->id())
->execute();
+ entity_get_controller('menu_link')->resetCache(array($links['parent']->id()));
$expected_hierarchy = array(
'child-1' => FALSE,
'child-1-1' => 'child-1',
@@ -214,8 +222,9 @@ function testMenuLinkRouterReparenting() {
// path.
// Don't do that at home.
db_delete('menu_links')
- ->condition('mlid', $links['child-2']['mlid'])
+ ->condition('mlid', $links['child-2']->id())
->execute();
+ entity_get_controller('menu_link')->resetCache(array($links['child-2']->id()));
$expected_hierarchy = array(
'child-1' => FALSE,
'child-1-1' => 'child-1',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php
index 094961c..85aa3f6 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php
@@ -304,30 +304,33 @@ function testMenuName() {
$admin_user = $this->drupalCreateUser(array('administer site configuration'));
$this->drupalLogin($admin_user);
- $sql = "SELECT menu_name FROM {menu_links} WHERE router_path = 'menu_name_test'";
- $name = db_query($sql)->fetchField();
- $this->assertEqual($name, 'original', 'Menu name is "original".');
+ $menu_link = entity_load_multiple_by_properties('menu_link', array('router_path' => 'menu_name_test'));
+ $menu_link = reset($menu_link);
+ $this->assertEqual($menu_link->menu_name, 'original', 'Menu name is "original".');
// Change the menu_name parameter in menu_test.module, then force a menu
// rebuild.
menu_test_menu_name('changed');
menu_router_rebuild();
- $sql = "SELECT menu_name FROM {menu_links} WHERE router_path = 'menu_name_test'";
- $name = db_query($sql)->fetchField();
- $this->assertEqual($name, 'changed', 'Menu name was successfully changed after rebuild.');
+ $menu_link = entity_load_multiple_by_properties('menu_link', array('router_path' => 'menu_name_test'));
+ $menu_link = reset($menu_link);
+ $this->assertEqual($menu_link->menu_name, 'changed', 'Menu name was successfully changed after rebuild.');
}
/**
* Tests for menu hierarchy.
*/
function testMenuHierarchy() {
- $parent_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent'))->fetchAssoc();
- $child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent/child'))->fetchAssoc();
- $unattached_child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent/child2/child'))->fetchAssoc();
-
- $this->assertEqual($child_link['plid'], $parent_link['mlid'], 'The parent of a directly attached child is correct.');
- $this->assertEqual($unattached_child_link['plid'], $parent_link['mlid'], 'The parent of a non-directly attached child is correct.');
+ $parent_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent'));
+ $parent_link = reset($parent_link);
+ $child_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child'));
+ $child_link = reset($child_link);
+ $unattached_child_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child2/child'));
+ $unattached_child_link = reset($unattached_child_link);
+
+ $this->assertEqual($child_link->plid, $parent_link->id(), 'The parent of a directly attached child is correct.');
+ $this->assertEqual($unattached_child_link->plid, $parent_link->id(), 'The parent of a non-directly attached child is correct.');
}
/**
@@ -335,88 +338,96 @@ function testMenuHierarchy() {
*/
function testMenuHidden() {
// Verify links for one dynamic argument.
- $links = db_select('menu_links', 'ml')
- ->fields('ml')
- ->condition('ml.router_path', 'menu-test/hidden/menu%', 'LIKE')
- ->orderBy('ml.router_path')
- ->execute()
- ->fetchAllAssoc('router_path', PDO::FETCH_ASSOC);
+ $query = menu_link_query()
+ ->propertyCondition('router_path', 'menu-test/hidden/menu', 'STARTS_WITH')
+ ->propertyOrderBy('router_path');
+ $result = $query->execute();
+ $menu_links = menu_link_load_multiple(array_keys($result['menu_link']));
+
+ $links = array();
+ foreach ($menu_links as $menu_link) {
+ $links[$menu_link->router_path] = $menu_link;
+ }
$parent = $links['menu-test/hidden/menu'];
- $depth = $parent['depth'] + 1;
- $plid = $parent['mlid'];
+ $depth = $parent->depth + 1;
+ $plid = $parent->id();
$link = $links['menu-test/hidden/menu/list'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/menu/add'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/menu/settings'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/menu/manage/%'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$parent = $links['menu-test/hidden/menu/manage/%'];
- $depth = $parent['depth'] + 1;
- $plid = $parent['mlid'];
+ $depth = $parent->depth + 1;
+ $plid = $parent->id();
$link = $links['menu-test/hidden/menu/manage/%/list'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/menu/manage/%/add'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/menu/manage/%/edit'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/menu/manage/%/delete'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
// Verify links for two dynamic arguments.
- $links = db_select('menu_links', 'ml')
- ->fields('ml')
- ->condition('ml.router_path', 'menu-test/hidden/block%', 'LIKE')
- ->orderBy('ml.router_path')
- ->execute()
- ->fetchAllAssoc('router_path', PDO::FETCH_ASSOC);
+ $query = menu_link_query()
+ ->propertyCondition('router_path', 'menu-test/hidden/block', 'STARTS_WITH')
+ ->propertyOrderBy('router_path');
+ $result = $query->execute();
+ $menu_links = menu_link_load_multiple(array_keys($result['menu_link']));
+
+ $links = array();
+ foreach ($menu_links as $menu_link) {
+ $links[$menu_link->router_path] = $menu_link;
+ }
$parent = $links['menu-test/hidden/block'];
- $depth = $parent['depth'] + 1;
- $plid = $parent['mlid'];
+ $depth = $parent->depth + 1;
+ $plid = $parent->id();
$link = $links['menu-test/hidden/block/list'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/block/add'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/block/manage/%/%'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$parent = $links['menu-test/hidden/block/manage/%/%'];
- $depth = $parent['depth'] + 1;
- $plid = $parent['mlid'];
+ $depth = $parent->depth + 1;
+ $plid = $parent->id();
$link = $links['menu-test/hidden/block/manage/%/%/configure'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
$link = $links['menu-test/hidden/block/manage/%/%/delete'];
- $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
- $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
+ $this->assertEqual($link->depth, $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link->router_path, '@link_depth' => $link->depth, '@depth' => $depth)));
+ $this->assertEqual($link->plid, $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link->router_path, '@link_plid' => $link->plid, '@plid' => $plid)));
}
/**
@@ -464,7 +475,7 @@ function testMenuItemHooks() {
*/
function testMenuLinkOptions() {
// Create a menu link with options.
- $menu_link = array(
+ $menu_link = entity_create('menu_link', array(
'link_title' => 'Menu link options test',
'link_path' => 'test-page',
'module' => 'menu_test',
@@ -476,8 +487,8 @@ function testMenuLinkOptions() {
'testparam' => 'testvalue',
),
),
- );
- menu_link_save($menu_link);
+ ));
+ $menu_link->save();
// Load front page.
$this->drupalGet('test-page');
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php
index eac8546..e1c518e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TrailTest.php
@@ -184,11 +184,11 @@ function testCustom403And404Pages() {
'@found' => count($initial_trail),
)));
foreach (array_keys($expected_trail[$status_code]['initial']) as $index => $path) {
- $this->assertEqual($initial_trail[$index]['href'], $path, format_string('Element number @number of the initial active trail for a @status_code page contains the correct path (expected: @expected, found: @found)', array(
+ $this->assertEqual($initial_trail[$index]->href, $path, format_string('Element number @number of the initial active trail for a @status_code page contains the correct path (expected: @expected, found: @found)', array(
'@number' => $index + 1,
'@status_code' => $status_code,
'@expected' => $path,
- '@found' => $initial_trail[$index]['href'],
+ '@found' => $initial_trail[$index]->href,
)));
}
@@ -201,11 +201,11 @@ function testCustom403And404Pages() {
'@found' => count($final_trail),
)));
foreach (array_keys($expected_trail[$status_code]['final']) as $index => $path) {
- $this->assertEqual($final_trail[$index]['href'], $path, format_string('Element number @number of the final active trail for a @status_code page contains the correct path (expected: @expected, found: @found)', array(
+ $this->assertEqual($final_trail[$index]->href, $path, format_string('Element number @number of the final active trail for a @status_code page contains the correct path (expected: @expected, found: @found)', array(
'@number' => $index + 1,
'@status_code' => $status_code,
'@expected' => $path,
- '@found' => $final_trail[$index]['href'],
+ '@found' => $final_trail[$index]->href,
)));
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
index fac7841..3f7f96a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Menu;
+use Drupal\menu_link\MenuLink;
use Drupal\simpletest\UnitTestBase;
/**
@@ -16,13 +17,7 @@ class TreeDataUnitTest extends UnitTestBase {
/**
* Dummy link structure acceptable for menu_tree_data().
*/
- var $links = array(
- 1 => array('mlid' => 1, 'depth' => 1),
- 2 => array('mlid' => 2, 'depth' => 1),
- 3 => array('mlid' => 3, 'depth' => 2),
- 4 => array('mlid' => 4, 'depth' => 3),
- 5 => array('mlid' => 5, 'depth' => 1),
- );
+ protected $links = array();
public static function getInfo() {
return array(
@@ -35,7 +30,15 @@ public static function getInfo() {
/**
* Validate the generation of a proper menu tree hierarchy.
*/
- function testMenuTreeData() {
+ public function testMenuTreeData() {
+ $this->links = array(
+ 1 => new MenuLink(array('mlid' => 1, 'depth' => 1), 'menu_link'),
+ 2 => new MenuLink(array('mlid' => 2, 'depth' => 1), 'menu_link'),
+ 3 => new MenuLink(array('mlid' => 3, 'depth' => 2), 'menu_link'),
+ 4 => new MenuLink(array('mlid' => 4, 'depth' => 3), 'menu_link'),
+ 5 => new MenuLink(array('mlid' => 5, 'depth' => 1), 'menu_link'),
+ );
+
$tree = menu_tree_data($this->links);
// Validate that parent items #1, #2, and #5 exist on the root level.
@@ -60,6 +63,6 @@ function testMenuTreeData() {
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertSameLink($link1, $link2, $message = '') {
- return $this->assert($link1['mlid'] == $link2['mlid'], $message ?: 'First link is identical to second link');
+ return $this->assert($link1->id() == $link2->id(), $message ?: 'First link is identical to second link');
}
}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
index e1b46ad..4ccc1f4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeOutputTest.php
@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Menu;
+use Drupal\menu_link\MenuLink;
use Drupal\simpletest\WebTestBase;
/**
@@ -16,24 +17,7 @@ class TreeOutputTest extends WebTestBase {
/**
* Dummy link structure acceptable for menu_tree_output().
*/
- var $tree_data = array(
- '1'=> array(
- 'link' => array( 'menu_name' => 'main-menu', 'mlid' => 1, 'hidden'=>0, 'has_children' => 1, 'title' => 'Item 1', 'in_active_trail' => 1, 'access'=>1, 'href' => 'a', 'localized_options' => array('attributes' => array('title' =>'')) ),
- 'below' => array(
- '2' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 2, 'hidden'=>0, 'has_children' => 1, 'title' => 'Item 2', 'in_active_trail' => 1, 'access'=>1, 'href' => 'a/b', 'localized_options' => array('attributes' => array('title' =>'')) ),
- 'below' => array(
- '3' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 3, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 3', 'in_active_trail' => 0, 'access'=>1, 'href' => 'a/b/c', 'localized_options' => array('attributes' => array('title' =>'')) ),
- 'below' => array() ),
- '4' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 4, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 4', 'in_active_trail' => 0, 'access'=>1, 'href' => 'a/b/d', 'localized_options' => array('attributes' => array('title' =>'')) ),
- 'below' => array() )
- )
- )
- )
- ),
- '5' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 5, 'hidden'=>1, 'has_children' => 0, 'title' => 'Item 5', 'in_active_trail' => 0, 'access'=>1, 'href' => 'e', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ),
- '6' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 6, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 6', 'in_active_trail' => 0, 'access'=>0, 'href' => 'f', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ),
- '7' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 7, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 7', 'in_active_trail' => 0, 'access'=>1, 'href' => 'g', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) )
- );
+ protected $tree_data = array();
public static function getInfo() {
return array(
@@ -51,6 +35,25 @@ function setUp() {
* Validate the generation of a proper menu tree output.
*/
function testMenuTreeData() {
+ $this->tree_data = array(
+ '1'=> array(
+ 'link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 1, 'hidden' => 0, 'has_children' => 1, 'title' => 'Item 1', 'in_active_trail' => 1, 'access' => 1, 'href' => 'a', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'),
+ 'below' => array(
+ '2' => array('link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 2, 'hidden' => 0, 'has_children' => 1, 'title' => 'Item 2', 'in_active_trail' => 1, 'access' => 1, 'href' => 'a/b', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'),
+ 'below' => array(
+ '3' => array('link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 3, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 3', 'in_active_trail' => 0, 'access' => 1, 'href' => 'a/b/c', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'),
+ 'below' => array() ),
+ '4' => array('link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 4, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 4', 'in_active_trail' => 0, 'access' => 1, 'href' => 'a/b/d', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'),
+ 'below' => array() )
+ )
+ )
+ )
+ ),
+ '5' => array('link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 5, 'hidden' => 1, 'has_children' => 0, 'title' => 'Item 5', 'in_active_trail' => 0, 'access' => 1, 'href' => 'e', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'), 'below' => array()),
+ '6' => array('link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 6, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'href' => 'f', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'), 'below' => array()),
+ '7' => array('link' => new MenuLink(array('menu_name' => 'main-menu', 'mlid' => 7, 'hidden' => 0, 'has_children' => 0, 'title' => 'Item 7', 'in_active_trail' => 0, 'access' => 1, 'href' => 'g', 'localized_options' => array('attributes' => array('title' =>''))), 'menu_link'), 'below' => array())
+ );
+
$output = menu_tree_output($this->tree_data);
// Validate that the - in main-menu is changed into an underscore
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index c55289c..d27dfa8 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -18,33 +18,39 @@ function system_admin_config_page() {
drupal_set_message(t('One or more problems were detected with your Drupal installation. Check the status report for more information.', array('@status' => url('admin/reports/status'))), 'error');
}
$blocks = array();
- if ($admin = db_query("SELECT menu_name, mlid FROM {menu_links} WHERE link_path = 'admin/config' AND module = 'system'")->fetchAssoc()) {
- $result = db_query("
- SELECT m.*, ml.*
- FROM {menu_links} ml
- INNER JOIN {menu_router} m ON ml.router_path = m.path
- WHERE ml.link_path <> 'admin/help' AND menu_name = :menu_name AND ml.plid = :mlid AND hidden = 0", $admin, array('fetch' => PDO::FETCH_ASSOC));
- foreach ($result as $item) {
- _menu_link_translate($item);
- if (!$item['access']) {
- continue;
- }
- // The link description, either derived from 'description' in hook_menu()
- // or customized via menu module is used as title attribute.
- if (!empty($item['localized_options']['attributes']['title'])) {
- $item['description'] = $item['localized_options']['attributes']['title'];
- unset($item['localized_options']['attributes']['title']);
- }
- $block = $item;
- $block['content'] = '';
- $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($item)));
- if (!empty($block['content'])) {
- $block['show'] = TRUE;
- }
+ if ($system_link = entity_load_multiple_by_properties('menu_link', array('link_path' => 'admin/config', 'module' => 'system'))) {
+ $system_link = reset($system_link);
+ $query = menu_link_query()
+ ->propertyCondition('link_path', 'admin/help', '<>')
+ ->propertyCondition('menu_name', $system_link->menu_name)
+ ->propertyCondition('plid', $system_link->id())
+ ->propertyCondition('hidden', 0);
+ $result = $query->execute();
+
+ if (!empty($result['menu_link'])) {
+ $menu_links = menu_link_load_multiple(array_keys($result['menu_link']));
+ foreach ($menu_links as $menu_link) {
+ if (!$menu_link->access()) {
+ continue;
+ }
+ // The link description, either derived from 'description' in hook_menu()
+ // or customized via menu module is used as title attribute.
+ // @todo Cleanup localized_options.
+// if (!empty($menu_link->localized_options['attributes']['title'])) {
+// $menu_link->description = $menu_link->localized_options['attributes']['title'];
+// unset($menu_link->localized_options['attributes']['title']);
+// }
+ $block = $menu_link->router_item;
+ $block['content'] = '';
+ $block['content'] .= theme('admin_block_content', array('content' => system_admin_menu_block($menu_link->router_item)));
+ if (!empty($block['content'])) {
+ $block['show'] = TRUE;
+ }
- // Prepare for sorting as in function _menu_tree_check_access().
- // The weight is offset so it is always positive, with a uniform 5-digits.
- $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
+ // Prepare for sorting as in function _menu_tree_check_access().
+ // The weight is offset so it is always positive, with a uniform 5-digits.
+ $blocks[(50000 + $menu_link->weight) . ' ' . $menu_link->label() . ' ' . $menu_link->id()] = $block;
+ }
}
}
if ($blocks) {
@@ -94,7 +100,7 @@ function system_admin_index() {
// Only display a section if there are any available tasks.
if ($admin_tasks = system_get_module_admin_tasks($module, $info->info)) {
// Sort links by title.
- uasort($admin_tasks, 'drupal_sort_title');
+ uasort($admin_tasks, 'drupal_sort_label');
// Move 'Configure permissions' links to the bottom of each section.
$permission_key = "admin/people/permissions#module-$module";
if (isset($admin_tasks[$permission_key])) {
@@ -2314,9 +2320,9 @@ function theme_admin_block_content($variables) {
}
$output .= '';
foreach ($content as $item) {
- $output .= '- ' . l($item['title'], $item['href'], $item['localized_options']) . '
';
- if (!$compact && isset($item['description'])) {
- $output .= '- ' . filter_xss_admin($item['description']) . '
';
+ $output .= '- ' . l($item->label(), $item->href, $item->localized_options) . '
';
+ if (!$compact && isset($item->description)) {
+ $output .= '- ' . filter_xss_admin($item->description) . '
';
}
}
$output .= '
';
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 0f6d668..630abf5 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -947,127 +947,6 @@ function hook_menu_alter(&$items) {
}
/**
- * Alter the data being saved to the {menu_links} table by menu_link_save().
- *
- * @param $item
- * Associative array defining a menu link as passed into menu_link_save().
- *
- * @see hook_translated_menu_link_alter()
- */
-function hook_menu_link_alter(&$item) {
- // Make all new admin links hidden (a.k.a disabled).
- if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) {
- $item['hidden'] = 1;
- }
- // Flag a link to be altered by hook_translated_menu_link_alter().
- if ($item['link_path'] == 'devel/cache/clear') {
- $item['options']['alter'] = TRUE;
- }
- // Flag a link to be altered by hook_translated_menu_link_alter(), but only
- // if it is derived from a menu router item; i.e., do not alter a custom
- // menu link pointing to the same path that has been created by a user.
- if ($item['link_path'] == 'user' && $item['module'] == 'system') {
- $item['options']['alter'] = TRUE;
- }
-}
-
-/**
- * Alter a menu link after it has been translated and before it is rendered.
- *
- * This hook is invoked from _menu_link_translate() after a menu link has been
- * translated; i.e., after dynamic path argument placeholders (%) have been
- * replaced with actual values, the user access to the link's target page has
- * been checked, and the link has been localized. It is only invoked if
- * $item['options']['alter'] has been set to a non-empty value (e.g., TRUE).
- * This flag should be set using hook_menu_link_alter().
- *
- * Implementations of this hook are able to alter any property of the menu link.
- * For example, this hook may be used to add a page-specific query string to all
- * menu links, or hide a certain link by setting:
- * @code
- * 'hidden' => 1,
- * @endcode
- *
- * @param $item
- * Associative array defining a menu link after _menu_link_translate()
- * @param $map
- * Associative array containing the menu $map (path parts and/or objects).
- *
- * @see hook_menu_link_alter()
- */
-function hook_translated_menu_link_alter(&$item, $map) {
- if ($item['href'] == 'devel/cache/clear') {
- $item['localized_options']['query'] = drupal_get_destination();
- }
-}
-
-/**
- * Inform modules that a menu link has been created.
- *
- * This hook is used to notify modules that menu items have been
- * created. Contributed modules may use the information to perform
- * actions based on the information entered into the menu system.
- *
- * @param $link
- * Associative array defining a menu link as passed into menu_link_save().
- *
- * @see hook_menu_link_update()
- * @see hook_menu_link_delete()
- */
-function hook_menu_link_insert($link) {
- // In our sample case, we track menu items as editing sections
- // of the site. These are stored in our table as 'disabled' items.
- $record['mlid'] = $link['mlid'];
- $record['menu_name'] = $link['menu_name'];
- $record['status'] = 0;
- drupal_write_record('menu_example', $record);
-}
-
-/**
- * Inform modules that a menu link has been updated.
- *
- * This hook is used to notify modules that menu items have been
- * updated. Contributed modules may use the information to perform
- * actions based on the information entered into the menu system.
- *
- * @param $link
- * Associative array defining a menu link as passed into menu_link_save().
- *
- * @see hook_menu_link_insert()
- * @see hook_menu_link_delete()
- */
-function hook_menu_link_update($link) {
- // If the parent menu has changed, update our record.
- $menu_name = db_query("SELECT menu_name FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $link['mlid']))->fetchField();
- if ($menu_name != $link['menu_name']) {
- db_update('menu_example')
- ->fields(array('menu_name' => $link['menu_name']))
- ->condition('mlid', $link['mlid'])
- ->execute();
- }
-}
-
-/**
- * Inform modules that a menu link has been deleted.
- *
- * This hook is used to notify modules that menu items have been
- * deleted. Contributed modules may use the information to perform
- * actions based on the information entered into the menu system.
- *
- * @param $link
- * Associative array defining a menu link as passed into menu_link_save().
- *
- * @see hook_menu_link_insert()
- * @see hook_menu_link_update()
- */
-function hook_menu_link_delete($link) {
- // Delete the record from our table.
- db_delete('menu_example')
- ->condition('mlid', $link['mlid'])
- ->execute();
-}
-
-/**
* Alter tabs and actions displayed on the page before they are rendered.
*
* This hook is invoked by menu_local_tasks(). The system-determined tabs and
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index a8819c1..f0ff3fe 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1029,192 +1029,6 @@ function system_schema() {
'primary key' => array('path'),
);
- $schema['menu_links'] = array(
- 'description' => 'Contains the individual links within a menu.',
- 'fields' => array(
- 'menu_name' => array(
- 'description' => "The menu name. All links with the same menu name (such as 'navigation') are part of the same menu.",
- 'type' => 'varchar',
- 'length' => 32,
- 'not null' => TRUE,
- 'default' => '',
- ),
- 'mlid' => array(
- 'description' => 'The menu link ID (mlid) is the integer primary key.',
- 'type' => 'serial',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- ),
- 'plid' => array(
- 'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'link_path' => array(
- 'description' => 'The Drupal path or external path this link points to.',
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => '',
- ),
- 'router_path' => array(
- 'description' => 'For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.',
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => '',
- ),
- 'link_title' => array(
- 'description' => 'The text displayed for the link, which may be modified by a title callback stored in {menu_router}.',
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => '',
- 'translatable' => TRUE,
- ),
- 'options' => array(
- 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
- 'type' => 'blob',
- 'not null' => FALSE,
- 'translatable' => TRUE,
- ),
- 'module' => array(
- 'description' => 'The name of the module that generated this link.',
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => 'system',
- ),
- 'hidden' => array(
- 'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- 'external' => array(
- 'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- 'has_children' => array(
- 'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- 'expanded' => array(
- 'description' => '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 (1 = expanded, 0 = not expanded)',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- 'weight' => array(
- 'description' => 'Link weight among links in the same menu at the same depth.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'depth' => array(
- 'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- 'customized' => array(
- 'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- 'p1' => array(
- 'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p2' => array(
- 'description' => 'The second mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p3' => array(
- 'description' => 'The third mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p4' => array(
- 'description' => 'The fourth mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p5' => array(
- 'description' => 'The fifth mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p6' => array(
- 'description' => 'The sixth mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p7' => array(
- 'description' => 'The seventh mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p8' => array(
- 'description' => 'The eighth mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'p9' => array(
- 'description' => 'The ninth mlid in the materialized path. See p1.',
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'default' => 0,
- ),
- 'updated' => array(
- 'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'size' => 'small',
- ),
- ),
- 'indexes' => array(
- 'path_menu' => array(array('link_path', 128), 'menu_name'),
- 'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'),
- 'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
- 'router_path' => array(array('router_path', 128)),
- ),
- 'primary key' => array('mlid'),
- );
-
$schema['queue'] = array(
'description' => 'Stores items in queues.',
'fields' => array(
@@ -2188,6 +2002,51 @@ function system_update_8030() {
}
/**
+ * Enable the Menu link module.
+ *
+ * Creates the langcode and UUID columns for menu links and adds the
+ * 'serialize' property to the options column.
+ */
+function system_update_8031() {
+ // Enable the module without re-installing the schema.
+ update_module_enable(array('menu_link'));
+
+ // Add the langcode column.
+ $column = array(
+ 'description' => 'The {language}.langcode of this entity.',
+ 'type' => 'varchar',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => '',
+ );
+ db_add_field('menu_links', 'langcode', $column);
+
+ // Add the UUID column.
+ $column = array(
+ 'description' => 'Unique Key: Universally unique identifier for this entity.',
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => FALSE,
+ );
+ $keys = array(
+ 'unique keys' => array(
+ 'uuid' => array('uuid'),
+ ),
+ );
+ db_add_field('menu_links', 'uuid', $column, $keys);
+
+ // Add the 'serialize' property to the options column.
+ $column = array(
+ 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.',
+ 'type' => 'blob',
+ 'not null' => FALSE,
+ 'translatable' => TRUE,
+ 'serialize' => TRUE,
+ );
+ db_change_field('menu_links', 'options', 'options', $column);
+}
+
+/**
* @} End of "defgroup updates-7.x-to-8.x".
* The next series of updates should start at 9000.
*/
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 255c669..ffa665c 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2575,7 +2575,10 @@ function system_admin_menu_block($item) {
}
if (!isset($item['mlid'])) {
- $item += db_query("SELECT mlid, menu_name FROM {menu_links} ml WHERE ml.router_path = :path AND module = 'system'", array(':path' => $item['path']))->fetchAssoc();
+ $menu_link = entity_load_multiple_by_properties('menu_link', array('router_path' => $item['path'], 'module' => 'system'));
+ $menu_link = reset($menu_link);
+ $item['mlid'] = $menu_link->id();
+ $item['menu_name'] = $menu_link->menu_name;
}
if (isset($cache[$item['mlid']])) {
@@ -2583,29 +2586,20 @@ function system_admin_menu_block($item) {
}
$content = array();
- $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
- $query->join('menu_router', 'm', 'm.path = ml.router_path');
- $query
- ->fields('ml')
- // Weight should be taken from {menu_links}, not {menu_router}.
- ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')))
- ->condition('ml.plid', $item['mlid'])
- ->condition('ml.menu_name', $item['menu_name'])
- ->condition('ml.hidden', 0);
-
- foreach ($query->execute() as $link) {
- _menu_link_translate($link);
- if ($link['access']) {
+ $menu_links = entity_load_multiple_by_properties('menu_link', array('plid' => $item['mlid'], 'menu_name' => $item['menu_name'], 'hidden' => 0));
+ foreach ($menu_links as $menu_link) {
+ if ($menu_link->access()) {
// The link description, either derived from 'description' in
// hook_menu() or customized via menu module is used as title attribute.
- if (!empty($link['localized_options']['attributes']['title'])) {
- $link['description'] = $link['localized_options']['attributes']['title'];
- unset($link['localized_options']['attributes']['title']);
- }
+ // @todo Cleanup localized_options.
+// if (!empty($menu_link->localized_options['attributes']['title'])) {
+// $menu_link->description = $menu_link->localized_options['attributes']['title'];
+// unset($menu_link->localized_options['attributes']['title']);
+// }
// Prepare for sorting as in function _menu_tree_check_access().
// The weight is offset so it is always positive, with a uniform 5-digits.
- $key = (50000 + $link['weight']) . ' ' . drupal_strtolower($link['title']) . ' ' . $link['mlid'];
- $content[$key] = $link;
+ $key = (50000 + $menu_link->weight) . ' ' . drupal_strtolower($menu_link->label()) . ' ' . $menu_link->id();
+ $content[$key] = $menu_link;
}
}
ksort($content);
@@ -3366,21 +3360,10 @@ function system_get_module_admin_tasks($module, $info) {
if (!isset($links)) {
$links = array();
- $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
- $query->join('menu_router', 'm', 'm.path = ml.router_path');
- $query
- ->fields('ml')
- // Weight should be taken from {menu_links}, not {menu_router}.
- ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')))
- ->condition('ml.link_path', 'admin/%', 'LIKE')
- ->condition('ml.hidden', 0, '>=')
- ->condition('ml.module', 'system')
- ->condition('m.number_parts', 1, '>')
- ->condition('m.page_callback', 'system_admin_menu_block_page', '<>');
- foreach ($query->execute() as $link) {
- _menu_link_translate($link);
- if ($link['access']) {
- $links[$link['router_path']] = $link;
+ $menu_links = entity_get_controller('menu_link')->loadModuleAdminTasks();
+ foreach ($menu_links as $menu_link) {
+ if ($menu_link->access()) {
+ $links[$menu_link->router_path] = $menu_link;
}
}
}
@@ -3393,27 +3376,28 @@ function system_get_module_admin_tasks($module, $info) {
$task = $links[$path];
// The link description, either derived from 'description' in
// hook_menu() or customized via menu module is used as title attribute.
- if (!empty($task['localized_options']['attributes']['title'])) {
- $task['description'] = $task['localized_options']['attributes']['title'];
- unset($task['localized_options']['attributes']['title']);
- }
+ // @todo Cleanup localized_options.
+// if (!empty($task['localized_options']['attributes']['title'])) {
+// $task['description'] = $task['localized_options']['attributes']['title'];
+// unset($task['localized_options']['attributes']['title']);
+// }
// Check the admin tasks for duplicate names. If one is found,
// append the parent menu item's title to differentiate.
- $duplicate_path = array_search($task['title'], $titles);
+ $duplicate_path = array_search($task->label(), $titles);
if ($duplicate_path !== FALSE) {
- if ($parent = menu_link_load($task['plid'])) {
+ if ($parent = menu_link_load($task->plid)) {
// Append the parent item's title to this task's title.
- $task['title'] = t('@original_title (@parent_title)', array('@original_title' => $task['title'], '@parent_title' => $parent['title']));
+ $task->title = t('@original_title (@parent_title)', array('@original_title' => $task->label(), '@parent_title' => $parent->label()));
}
- if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) {
+ if ($parent = menu_link_load($admin_tasks[$duplicate_path]->plid)) {
// Append the parent item's title to the duplicated task's title.
// We use $links[$duplicate_path] in case there are triplicates.
- $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title']));
+ $admin_tasks[$duplicate_path]->title = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]->label(), '@parent_title' => $parent->label()));
}
}
else {
- $titles[$path] = $task['title'];
+ $titles[$path] = $task->label();
}
$admin_tasks[$path] = $task;
@@ -3428,7 +3412,9 @@ function system_get_module_admin_tasks($module, $info) {
$item['link_path'] = $item['href'];
$item['title'] = t('Configure @module permissions', array('@module' => $info['name']));
unset($item['description']);
+ // @todo Cleanup localized_options.
$item['localized_options']['fragment'] = 'module-' . $module;
+ $item = entity_create('menu_link', $item);
$admin_tasks["admin/people/permissions#module-$module"] = $item;
}
}
diff --git a/core/modules/toolbar/toolbar.info b/core/modules/toolbar/toolbar.info
index 758dc9c..bc7a75f 100644
--- a/core/modules/toolbar/toolbar.info
+++ b/core/modules/toolbar/toolbar.info
@@ -3,3 +3,4 @@ description = Provides a toolbar that shows the top-level administration menu it
core = 8.x
package = Core
version = VERSION
+dependencies[] = menu_link
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index 3717d7b..3b5a561 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -278,12 +278,17 @@ function toolbar_view() {
*/
function toolbar_get_menu_tree() {
$tree = array();
- $admin_link = db_query('SELECT * FROM {menu_links} WHERE menu_name = :menu_name AND module = :module AND link_path = :path', array(':menu_name' => 'management', ':module' => 'system', ':path' => 'admin'))->fetchAssoc();
- if ($admin_link) {
+ $query = menu_link_query()
+ ->propertyCondition('menu_name', 'management')
+ ->propertyCondition('module', 'system')
+ ->propertyCondition('link_path', 'admin');
+ $result = $query->execute();
+ if (!empty($result['menu_link'])) {
+ $admin_link = menu_link_load(key($result['menu_link']));
$tree = menu_build_tree('management', array(
- 'expanded' => array($admin_link['mlid']),
- 'min_depth' => $admin_link['depth'] + 1,
- 'max_depth' => $admin_link['depth'] + 1,
+ 'expanded' => array($admin_link->id()),
+ 'min_depth' => $admin_link->depth + 1,
+ 'max_depth' => $admin_link->depth + 1,
));
}
@@ -302,28 +307,28 @@ function toolbar_get_menu_tree() {
function toolbar_menu_navigation_links($tree) {
$links = array();
foreach ($tree as $item) {
- if (!$item['link']['hidden'] && $item['link']['access']) {
+ if (!$item['link']->hidden && $item['link']->access()) {
// Make sure we have a path specific ID in place, so we can attach icons
// and behaviors to the items.
- $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['href']);
+ $id = str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']->href);
- $link = $item['link']['localized_options'];
- $link['href'] = $item['link']['href'];
+ $link = $item['link']->options;
+ $link['href'] = $item['link']->href;
// Add icon placeholder.
- $link['title'] = '' . check_plain($item['link']['title']);
+ $link['title'] = '' . check_plain($item['link']->label());
// Add admin link ID.
$link['attributes'] = array('id' => 'toolbar-link-' . $id);
- if (!empty($item['link']['description'])) {
- $link['title'] .= ' (' . $item['link']['description'] . ')';
- $link['attributes']['title'] = $item['link']['description'];
+ if (!empty($item['link']->description)) {
+ $link['title'] .= ' (' . $item['link']->description . ')';
+ $link['attributes']['title'] = $item['link']->description;
}
$link['html'] = TRUE;
$class = ' path-' . $id;
- if (toolbar_in_active_trail($item['link']['href'])) {
+ if (toolbar_in_active_trail($item['link']->href)) {
$class .= ' active-trail';
}
- $links['menu-' . $item['link']['mlid'] . $class] = $link;
+ $links['menu-' . $item['link']->id() . $class] = $link;
}
}
return $links;
@@ -349,8 +354,8 @@ function toolbar_in_active_trail($path) {
$active_paths = array();
$trail = menu_get_active_trail();
foreach ($trail as $item) {
- if (!empty($item['href'])) {
- $active_paths[] = $item['href'];
+ if (!empty($item->href)) {
+ $active_paths[] = $item->href;
}
}
}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index fae45ad..bb5806e 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1400,21 +1400,21 @@ function user_menu_site_status_alter(&$menu_site_status, $path) {
}
/**
- * Implements hook_menu_link_alter().
+ * Implements hook_menu_link_presave().
*/
-function user_menu_link_alter(&$link) {
+function user_menu_link_presave($menu_link) {
// The path 'user' must be accessible for anonymous users, but only visible
// for authenticated users. Authenticated users should see "My account", but
// anonymous users should not see it at all. Therefore, invoke
- // user_translated_menu_link_alter() to conditionally hide the link.
- if ($link['link_path'] == 'user' && $link['module'] == 'system') {
- $link['options']['alter'] = TRUE;
+ // user_menu_link_load() to conditionally hide the link.
+ if ($menu_link->link_path == 'user' && $menu_link->module == 'system') {
+ $menu_link->options['alter'] = TRUE;
}
// Force the Logout link to appear on the top-level of 'user-menu' menu by
// default (i.e., unless it has been customized).
- if ($link['link_path'] == 'user/logout' && $link['module'] == 'system' && empty($link['customized'])) {
- $link['plid'] = 0;
+ if ($menu_link->link_path == 'user/logout' && $menu_link->module == 'system' && empty($menu_link->customized)) {
+ $menu_link->plid = 0;
}
}
@@ -1424,18 +1424,20 @@ function user_menu_link_alter(&$link) {
function user_menu_breadcrumb_alter(&$active_trail, $item) {
// Remove "My account" from the breadcrumb when $item is descendant-or-self
// of system path user/%.
- if (isset($active_trail[1]['module']) && $active_trail[1]['module'] == 'system' && $active_trail[1]['link_path'] == 'user' && strpos($item['path'], 'user/%') === 0) {
+ if (isset($active_trail[1]->module) && $active_trail[1]->module == 'system' && $active_trail[1]->link_path == 'user' && strpos($item['path'], 'user/%') === 0) {
array_splice($active_trail, 1, 1);
}
}
/**
- * Implements hook_translated_menu_link_alter().
+ * Implements hook_menu_link_load().
*/
-function user_translated_menu_link_alter(&$link) {
+function user_menu_link_load($menu_links) {
// Hide the "User account" link for anonymous users.
- if ($link['link_path'] == 'user' && $link['module'] == 'system' && !$GLOBALS['user']->uid) {
- $link['hidden'] = 1;
+ foreach ($menu_links as $menu_link) {
+ if ($menu_link->link_path == 'user' && $menu_link->module == 'system' && !$GLOBALS['user']->uid) {
+ $menu_link->hidden = 1;
+ }
}
}
diff --git a/core/profiles/minimal/minimal.info b/core/profiles/minimal/minimal.info
index 545e85c..5276bb2 100644
--- a/core/profiles/minimal/minimal.info
+++ b/core/profiles/minimal/minimal.info
@@ -2,6 +2,7 @@ name = Minimal
description = Build a custom site without pre-configured functionality. Suitable for advanced users.
version = VERSION
core = 8.x
+dependencies[] = menu_link
dependencies[] = node
dependencies[] = block
dependencies[] = dblog
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 4eeb8d6..c28447d 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -394,25 +394,25 @@ function standard_install() {
->execute();
// Create a Home link in the main menu.
- $item = array(
+ $menu_link = entity_create('menu_link', array(
'link_title' => st('Home'),
'link_path' => '',
'menu_name' => 'main-menu',
- );
- menu_link_save($item);
+ ));
+ $menu_link->save();
// Populate the default shortcut set.
$shortcut_set = shortcut_set_load(SHORTCUT_DEFAULT_SET_NAME);
- $shortcut_set->links[] = array(
+ $shortcut_set->links[] = entity_create('menu_link', array(
'link_path' => 'node/add',
'link_title' => st('Add content'),
'weight' => -20,
- );
- $shortcut_set->links[] = array(
+ ));
+ $shortcut_set->links[] = entity_create('menu_link', array(
'link_path' => 'admin/content',
'link_title' => st('Find content'),
'weight' => -19,
- );
+ ));
shortcut_set_save($shortcut_set);
// Update the menu router information.
diff --git a/core/profiles/testing/testing.info b/core/profiles/testing/testing.info
index fff3df2..fcbe479 100644
--- a/core/profiles/testing/testing.info
+++ b/core/profiles/testing/testing.info
@@ -3,5 +3,6 @@ description = Minimal profile for running tests. Includes absolutely required mo
version = VERSION
core = 8.x
hidden = TRUE
+dependencies[] = menu_link
; @todo Remove dependency on Node module.
dependencies[] = node
diff --git a/core/themes/seven/template.php b/core/themes/seven/template.php
index a8643ab..3295faa 100644
--- a/core/themes/seven/template.php
+++ b/core/themes/seven/template.php
@@ -71,9 +71,11 @@ function seven_admin_block_content($variables) {
$output = system_admin_compact_mode() ? '