summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--includes/menu.inc242
-rw-r--r--modules/menu/menu.module557
-rw-r--r--modules/system/system.install1
-rw-r--r--modules/system/system.schema1
-rw-r--r--profiles/default/default.profile2
5 files changed, 496 insertions, 307 deletions
diff --git a/includes/menu.inc b/includes/menu.inc
index f96641273..ec3e2e93f 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -67,7 +67,7 @@
*
* Everything described so far is stored in the menu_router table. The
* menu_links table holds the visible menu links. By default these are
- * derived from the same hook_menu definitions, however you are free to
+ * derived from the same hook_menu definitons, however you are free to
* add more with menu_link_save().
*/
@@ -297,7 +297,6 @@ function menu_get_item($path = NULL) {
/**
* Execute the page callback associated with the current path
*/
-
function menu_execute_active_handler($path = NULL) {
if (_menu_site_is_offline()) {
return MENU_SITE_OFFLINE;
@@ -378,6 +377,9 @@ function _menu_check_access(&$item, $map) {
}
}
+/**
+ * Localize the item title using t() or another callback.
+ */
function _menu_item_localize(&$item) {
// Translate the title to allow storage of English title strings
// in the database, yet display of them in the language required
@@ -514,15 +516,15 @@ function _menu_link_translate(&$item) {
_menu_link_map_translate($map, $item['to_arg_functions']);
$item['href'] = implode('/', $map);
- // Note- skip callbacks without real values for their arguments
+ // Note- skip callbacks without real values for their arguments.
if (strpos($item['href'], '%') !== FALSE) {
$item['access'] = FALSE;
return FALSE;
}
- // TODO: menu_tree_data may set this ahead of time for links to nodes
+ // menu_tree_check_access() may set this ahead of time for links to nodes.
if (!isset($item['access'])) {
if (!_menu_load_objects($item, $map)) {
- // An error occurred loading an object
+ // An error occured loading an object.
$item['access'] = FALSE;
return FALSE;
}
@@ -606,16 +608,22 @@ function menu_tree_output($tree) {
function menu_tree_all_data($menu_name = 'navigation', $item = NULL, $show_hidden = FALSE) {
static $tree = array();
+ // Use $mlid as a flag for whether the data being loaded is for the whole tree.
$mlid = isset($item['mlid']) ? $item['mlid'] : 0;
+ // Generate the cache ID.
$cid = 'links:'. $menu_name .':all:'. $mlid .':'. (int)$show_hidden;
if (!isset($tree[$cid])) {
+ // If the static variable doesn't have the data, check {cache_menu}.
$cache = cache_get($cid, 'cache_menu');
if ($cache && isset($cache->data)) {
$tree[$cid] = $cache->data;
}
else {
+ // Build and run the query, and build the tree.
if ($mlid) {
+ // The tree is for a single item, so we need to match the values in its
+ // p columns and 0 (the top level) with the plid values of other links.
$args = array(0, $item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5']);
$args = array_unique($args);
$placeholders = implode(', ', array_fill(0, count($args), '%d'));
@@ -624,26 +632,28 @@ function menu_tree_all_data($menu_name = 'navigation', $item = NULL, $show_hidde
$parents[] = $item['mlid'];
}
else {
+ // Get all links in this menu.
$where = '';
$args = array();
$parents = array();
}
- if (!$show_hidden) {
- $where .= ' AND ml.hidden = 0';
- }
- else {
- $where .= ' AND ml.hidden > 0';
- }
array_unshift($args, $menu_name);
- list(, $tree[$cid]) = _menu_tree_data(db_query("
+ // 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.
+ // We need to select links that are visible or hidden (ml.hidden >= 0), but
+ // not callbacks (ml.hidden < 0), so that we can later exclude all the
+ // children of a hidden item.
+ // No need to order by p6 - there is a sort by weight later.
+ $tree[$cid] = menu_tree_data(db_query("
SELECT m.*, ml.menu_name, ml.mlid, ml.plid, ml.link_path, ml.router_path, ml.hidden, ml.external, ml.has_children, ml.expanded, ml.weight + 50000 AS weight, ml.depth, ml.p1, ml.p2, ml.p3, ml.p4, ml.p5, ml.p6, ml.module, ml.link_title, ml.options
FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
- WHERE ml.menu_name = '%s'". $where ."
+ WHERE ml.menu_name = '%s'". $where ." AND ml.hidden >= 0
ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC", $args), $parents);
+ // Cache the data.
cache_set($cid, $tree[$cid], 'cache_menu');
}
- // TODO: special case node links and access check via db_rewite_sql()
- _menu_tree_check_access($tree[$cid]);
+ // Check access for the current user to each item in the tree.
+ menu_tree_check_access($tree[$cid], $show_hidden);
}
return $tree[$cid];
@@ -651,7 +661,7 @@ function menu_tree_all_data($menu_name = 'navigation', $item = NULL, $show_hidde
/**
* Get the data structure representing a named menu tree, based on the current
- * page. The tree order is maintained by storing each parent in an individual
+ * page. The tree order is maintained by storing each parent in an invidual
* field, see http://drupal.org/node/141866 for more.
*
* @param $menu_name
@@ -666,28 +676,38 @@ function menu_tree_all_data($menu_name = 'navigation', $item = NULL, $show_hidde
function menu_tree_page_data($menu_name = 'navigation') {
static $tree = array();
+ // Load the menu item corresponding to the current page.
if ($item = menu_get_item()) {
+ // Generate the cache ID.
$cid = 'links:'. $menu_name .':page:'. $item['href'] .':'. (int)$item['access'];
if (!isset($tree[$cid])) {
+ // If the static variable doesn't have the data, check {cache_menu}.
$cache = cache_get($cid, 'cache_menu');
if ($cache && isset($cache->data)) {
$tree[$cid] = $cache->data;
}
else {
+ // Build and run the query, and build the tree.
if ($item['access']) {
+ // Check whether a menu link exists that corresponds to the current path.
$parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['href']));
- // We may be on a local task that's not in the links
- // TODO how do we handle the case like a local task on a specific node in the menu?
+
if (empty($parents)) {
+ // If no link exists, we may be on a local task that's not in the links.
+ // TODO: handle the case like a local task on a specific node in the menu.
$parents = db_fetch_array(db_query("SELECT p1, p2, p3, p4, p5, p6 FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['tab_root']));
}
+ // We always want all the top-level links with plid == 0.
$parents[] = '0';
$args = $parents = array_unique($parents);
$placeholders = implode(', ', array_fill(0, count($args), '%d'));
$expanded = variable_get('menu_expanded', array());
+ // Check whether the current menu has any links set to be expanded.
if (in_array($menu_name, $expanded)) {
+ // Collect all the links set to be expanded, and then add all their
+ // children to the list also.
do {
$result = db_query("SELECT mlid FROM {menu_links} WHERE expanded != 0 AND has_children != 0 AND menu_name = '%s' AND plid IN (". $placeholders .') AND mlid NOT IN ('. $placeholders .')', array_merge(array($menu_name), $args, $args));
while ($item = db_fetch_array($result)) {
@@ -698,23 +718,28 @@ function menu_tree_page_data($menu_name = 'navigation') {
}
array_unshift($args, $menu_name);
}
- // Show the root menu for access denied.
else {
- $args = array('navigation', '0');
+ // Show only the top-level menu items when access is denied.
+ $args = array($menu_name, '0');
$placeholders = '%d';
$parents = array();
}
+ // 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.
+ // We need to select links that are visible or hidden (ml.hidden >= 0), but
+ // not callbacks (ml.hidden < 0), so that we can later exclude all the
+ // children of a hidden item.
// No need to order by p6 - there is a sort by weight later.
- list(, $tree[$cid]) = _menu_tree_data(db_query("
+ $tree[$cid] = menu_tree_data(db_query("
SELECT m.*, ml.menu_name, ml.mlid, ml.plid, ml.link_path, ml.router_path, ml.hidden, ml.external, ml.has_children, ml.expanded, ml.weight + 50000 AS weight, ml.depth, ml.p1, ml.p2, ml.p3, ml.p4, ml.p5, ml.p6, ml.module, ml.link_title, ml.options
FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
- WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .") AND ml.hidden = 0
+ WHERE ml.menu_name = '%s' AND ml.plid IN (". $placeholders .") AND ml.hidden >= 0
ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC", $args), $parents);
+ // Cache the data.
cache_set($cid, $tree[$cid], 'cache_menu');
}
- // TODO: special case node links and access check via db_rewite_sql()
- _menu_tree_check_access($tree[$cid]);
+ // Check access for the current user to each item in the tree.
+ menu_tree_check_access($tree[$cid]);
}
return $tree[$cid];
}
@@ -722,15 +747,34 @@ function menu_tree_page_data($menu_name = 'navigation') {
return array();
}
-function _menu_tree_check_access(&$tree) {
+/**
+ * Check access and perform other dynamic operations for each link in the tree.
+ */
+function menu_tree_check_access(&$tree, $show_hidden = FALSE) {
+ // TODO: special case node links and access check via db_rewite_sql().
+ // TODO: move sorting of siblings in the tree here so that we can sort based on
+ // the localized title of each link. Currently the dorting is done in
+ // function _menu_tree_data().
+ _menu_tree_check_access($tree, $show_hidden);
+}
+
+/**
+ * Recursive helper function for menu_tree_check_access()
+ */
+function _menu_tree_check_access(&$tree, $show_hidden) {
foreach ($tree as $key => $v) {
$item = &$tree[$key]['link'];
- _menu_link_translate($item);
+ if (!$item['hidden'] || $show_hidden) {
+ _menu_link_translate($item);
+ }
+ else {
+ $item['access'] = FALSE;
+ }
if (!$item['access']) {
unset($tree[$key]);
}
elseif ($tree[$key]['below']) {
- _menu_tree_check_access($tree[$key]['below']);
+ _menu_tree_check_access($tree[$key]['below'], $show_hidden);
}
}
}
@@ -738,10 +782,6 @@ function _menu_tree_check_access(&$tree) {
/**
* Build the data representing a menu tree.
*
- * The function is a bit complex because the rendering of an item depends on
- * the next menu item. So we are always rendering the element previously
- * processed not the current one.
- *
* @param $result
* The database result.
* @param $parents
@@ -749,12 +789,23 @@ function _menu_tree_check_access(&$tree) {
* to the root of the menu tree.
* @param $depth
* The depth of the current menu tree.
- * @param $previous_element
- * The previous menu link in the current menu tree.
* @return
- * See menu_tree_data for a description of the data structure.
+ * See menu_tree_page_data for a description of the data structure.
+ */
+function menu_tree_data($result = NULL, $parents = array(), $depth = 1) {
+
+ list(, $tree) = _menu_tree_data($result, $parents, $depth);
+ return $tree;
+}
+
+/**
+ * Recursive helper function to build the data representing a menu tree.
+ *
+ * The function is a bit complex because the rendering of an item depends on
+ * the next menu item. So we are always rendering the element previously
+ * processed not the current one.
*/
-function _menu_tree_data($result = NULL, $parents = array(), $depth = 1, $previous_element = '') {
+function _menu_tree_data($result, $parents, $depth, $previous_element = '') {
$remnant = NULL;
$tree = array();
while ($item = db_fetch_array($result)) {
@@ -763,7 +814,7 @@ function _menu_tree_data($result = NULL, $parents = array(), $depth = 1, $previo
$item['in_active_trail'] = in_array($item['mlid'], $parents);
// The weights are uniform 5 digits because of the 50000 offset in the
// query. We add mlid at the end of the index to insure uniqueness.
- $index = $previous_element ? ($previous_element['weight'] .' '. $previous_element['title'] . $previous_element['mlid']) : '';
+ $index = $previous_element ? ($previous_element['weight'] .' '. drupal_strtolower($previous_element['link_title']) . $previous_element['mlid']) : '';
// The current item is the first in a new submenu.
if ($item['depth'] > $depth) {
// _menu_tree returns an item and the menu tree structure.
@@ -798,8 +849,8 @@ function _menu_tree_data($result = NULL, $parents = array(), $depth = 1, $previo
}
}
if ($previous_element) {
- // We have one more link dangling.
- $tree[$previous_element['weight'] .' '. $previous_element['title'] .' '. $previous_element['mlid']] = array(
+ // We have one more link dangling
+ $tree[$previous_element['weight'] .' '. drupal_strtolower($previous_element['link_title']) .' '. $previous_element['mlid']] = array(
'link' => $previous_element,
'below' => '',
);
@@ -833,6 +884,9 @@ function theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FA
return '<li class="'. $class .'">'. $link . $menu .'</li>'."\n";
}
+/**
+ * Generate the HTML output for a single local task link.
+ */
function theme_menu_local_task($link, $active = FALSE) {
return '<li '. ($active ? 'class="active" ' : '') .'>'. $link .'</li>';
}
@@ -886,6 +940,9 @@ function menu_get_names($reset = FALSE) {
return $names;
}
+/**
+ * Return an array of links to be rendered as the Primary links.
+ */
function menu_primary_links() {
$tree = menu_tree_page_data('primary-links');
$links = array();
@@ -898,6 +955,9 @@ function menu_primary_links() {
return $links;
}
+/**
+ * Return an array of links to be rendered as the Secondary links.
+ */
function menu_secondary_links() {
$tree = menu_tree_page_data('secondary-links');
$links = array();
@@ -941,7 +1001,7 @@ function menu_local_tasks($level = 0, $return_root = FALSE) {
while ($item = db_fetch_array($result)) {
_menu_translate($item, $map, TRUE);
if ($item['tab_parent']) {
- // All tabs, but not the root page.
+ // All tabs, but not the root page
$children[$item['tab_parent']][$item['path']] = $item;
}
// Store the translated item for later use.
@@ -976,7 +1036,7 @@ function menu_local_tasks($level = 0, $return_root = FALSE) {
$tabs[$item['number_parts']]['output'] = $tabs_current;
}
- // Find all tabs at the same level or above the current one
+ // Find all tabs at the same level or above the current one.
$parent = $router_item['tab_parent'];
$path = $router_item['path'];
$current = $router_item;
@@ -1032,10 +1092,16 @@ function menu_local_tasks($level = 0, $return_root = FALSE) {
}
}
+/**
+ * Returns the rendered local tasks at the top level.
+ */
function menu_primary_local_tasks() {
return menu_local_tasks(0);
}
+/**
+ * Returns the rendered local tasks at the second level.
+ */
function menu_secondary_local_tasks() {
return menu_local_tasks(1);
}
@@ -1065,6 +1131,9 @@ function theme_menu_local_tasks() {
return $output;
}
+/**
+ * Set (or get) the active menu for the current page - determines the active trail.
+ */
function menu_set_active_menu_name($menu_name = NULL) {
static $active;
@@ -1077,6 +1146,9 @@ function menu_set_active_menu_name($menu_name = NULL) {
return $active;
}
+/**
+ * Get the active menu for the current page - determines the active trail.
+ */
function menu_get_active_menu_name() {
return menu_set_active_menu_name();
}
@@ -1084,6 +1156,9 @@ function menu_get_active_menu_name() {
function menu_set_active_item() {
}
+/**
+ * Set (or get) the active trail for the current page - the path to root in the menu tree..
+ */
function menu_set_active_trail($new_trail = NULL) {
static $trail;
@@ -1121,6 +1196,9 @@ function menu_set_active_trail($new_trail = NULL) {
return $trail;
}
+/**
+ * Get the active trail for the current page - the path to root in the menu tree..
+ */
function menu_get_active_trail() {
return menu_set_active_trail();
}
@@ -1128,6 +1206,9 @@ function menu_get_active_trail() {
function menu_set_location() {
}
+/**
+ * Get the breadcrumb for the current page, as determined by the active trail.
+ */
function menu_get_active_breadcrumb() {
$breadcrumb = array();
$item = menu_get_item();
@@ -1147,6 +1228,9 @@ function menu_get_active_breadcrumb() {
return $breadcrumb;
}
+/**
+ * Get the title of the current page, as determined by the active trail.
+ */
function menu_get_active_title() {
$active_trail = menu_get_active_trail();
@@ -1168,20 +1252,23 @@ function menu_get_active_title() {
* rendering.
*/
function menu_link_load($mlid) {
- if ($item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) {
+ if (is_numeric($mlid) && $item = db_fetch_array(db_query("SELECT m.*, ml.* FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid))) {
_menu_link_translate($item);
return $item;
}
return FALSE;
}
+/**
+ * Clears the cached cached data for a single named menu.
+ */
function menu_cache_clear($menu_name = 'navigation') {
cache_clear_all('links:'. $menu_name .':', 'cache_menu', TRUE);
}
/**
- * This should be called any time broad changes might have been made to the
- * router items or menu links.
+ * Clears all cached menu data. This should be called any time broad changes
+ * might have been made to the router items or menu links.
*/
function menu_cache_clear_all() {
cache_clear_all('*', 'cache_menu', TRUE);
@@ -1253,13 +1340,15 @@ function _menu_link_build($item) {
return $item;
}
+/**
+ * Helper function to build 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) {
- $item = _menu_link_build($item);
- // We add nonexisting items.
- if ($item['_visible'] && !db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $item['menu_name'], $item['link_path']))) {
+ if ($item['_visible']) {
+ $item = _menu_link_build($item);
$menu_links[$path] = $item;
$sort[$path] = $item['_number_parts'];
}
@@ -1269,7 +1358,13 @@ function _menu_navigation_links_rebuild($menu) {
array_multisort($sort, SORT_NUMERIC, $menu_links);
foreach ($menu_links as $item) {
- menu_link_save($item);
+ $existing_item = db_fetch_array(db_query("SELECT mlid, customized FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s' AND module = 'system'", $item['menu_name'], $item['link_path']));
+ if ($existing_item) {
+ $item['mlid'] = $existing_item['mlid'];
+ }
+ if (!$existing_item || !$existing_item['customized']) {
+ menu_link_save($item);
+ }
}
}
$placeholders = implode(', ', array_fill(0, count($menu), "'%s'"));
@@ -1297,6 +1392,9 @@ function menu_link_delete($mlid, $path = NULL) {
}
}
+/**
+ * Helper function for menu_link_delete; deletes a single menu link.
+ */
function _menu_delete_item($item) {
// System-created items get automatically deleted, but only on menu rebuild.
if ($item && $item['module'] != 'system') {
@@ -1331,8 +1429,7 @@ function _menu_delete_item($item) {
* weight default is 0
* expanded whether the item is expanded.
* options An array of options, @see l for more.
- * mlid If it's an existing item, this comes from the database.
- * Never set by hand.
+ * mlid Set to an existing value, or 0 or NULL to insert a new link.
* plid The mlid of the parent.
* router_path The path of the relevant router item.
*/
@@ -1341,8 +1438,10 @@ function menu_link_save(&$item) {
drupal_alter('menu_link', $item, $menu);
- $item['_external'] = menu_path_is_external($item['link_path']);
- // Load defaults.
+ // This is the easiest way to handle the unique internal path '<front>',
+ // since a path marked as external does not need to match a router path.
+ $item['_external'] = menu_path_is_external($item['link_path']) || $item['link_path'] == '<front>';
+ // Load defaults
$item += array(
'menu_name' => 'navigation',
'weight' => 0,
@@ -1352,19 +1451,13 @@ function menu_link_save(&$item) {
'expanded' => 0,
'options' => array(),
'module' => 'menu',
+ 'customized' => 0,
);
$menu_name = $item['menu_name'];
$existing_item = FALSE;
if (isset($item['mlid'])) {
$existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['mlid']));
}
- else {
- $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND link_path = '%s'", $menu_name, $item['link_path']));
- }
-
- if ($existing_item) {
- $item['mlid'] = $existing_item['mlid'];
- }
// Find the parent - it must be in the same menu.
if (isset($item['plid'])) {
@@ -1378,7 +1471,7 @@ function menu_link_save(&$item) {
} while ($parent === FALSE && $parent_path);
}
// Menu callbacks need to be in the links table for breadcrumbs, but can't
- // be parents if they are generated directly from a router item
+ // be parents if they are generated directly from a router item.
if (empty($parent['mlid']) || $parent['hidden'] < 0) {
$item['plid'] = 0;
}
@@ -1391,15 +1484,15 @@ function menu_link_save(&$item) {
menu_name, plid, link_path,
hidden, external, has_children,
expanded, weight,
- module, link_title, options) VALUES (
+ module, link_title, options, customized) VALUES (
'%s', %d, '%s',
%d, %d, %d,
%d, %d,
- '%s', '%s', '%s')",
+ '%s', '%s', '%s', %d)",
$item['menu_name'], $item['plid'], $item['link_path'],
$item['hidden'], $item['_external'], $item['has_children'],
$item['expanded'], $item['weight'],
- $item['module'], $item['link_title'], serialize($item['options']));
+ $item['module'], $item['link_title'], serialize($item['options']), $item['customized']);
$item['mlid'] = db_last_insert_id('menu_links', 'mlid');
}
@@ -1409,7 +1502,7 @@ function menu_link_save(&$item) {
$item['depth'] = 1;
}
else {
- // Cannot add beyond the maximum depth.
+ // Cannot add beyond the maximum depth
if ($item['has_children'] && $existing_item) {
$limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1;
}
@@ -1450,12 +1543,12 @@ function menu_link_save(&$item) {
router_path = '%s', hidden = %d, external = %d, has_children = %d,
expanded = %d, weight = %d, depth = %d,
p1 = %d, p2 = %d, p3 = %d, p4 = %d, p5 = %d, p6 = %d,
- module = '%s', link_title = '%s', options = '%s' WHERE mlid = %d",
+ module = '%s', link_title = '%s', options = '%s', customized = %d WHERE mlid = %d",
$item['menu_name'], $item['plid'], $item['link_path'],
$item['router_path'], $item['hidden'], $item['_external'], $item['has_children'],
$item['expanded'], $item['weight'], $item['depth'],
$item['p1'], $item['p2'], $item['p3'], $item['p4'], $item['p5'], $item['p6'],
- $item['module'], $item['link_title'], serialize($item['options']), $item['mlid']);
+ $item['module'], $item['link_title'], serialize($item['options']), $item['customized'], $item['mlid']);
// Check the has_children status of the parent.
if ($item['plid']) {
$parent_has_children = (bool)db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE plid = %d AND hidden = 0", $item['plid']));
@@ -1552,6 +1645,9 @@ function _menu_link_move_children($item, $existing_item) {
}
}
+/**
+ * Helper function that sets the p1..p6 values for a menu link being saved.
+ */
function _menu_link_parents_set(&$item, $parent) {
$i = 1;
while ($i < $item['depth']) {
@@ -1567,6 +1663,9 @@ function _menu_link_parents_set(&$item, $parent) {
}
}
+/**
+ * Helper function to build the router table based on the data from hook_menu.
+ */
function _menu_router_build($callbacks) {
// First pass: separate callbacks from paths, making paths ready for
// matching. Calculate fitness, and fill some default values.
@@ -1582,7 +1681,7 @@ function _menu_router_build($callbacks) {
// We store the highest index of parts here to save some work in the fit
// calculation loop.
$slashes = $number_parts - 1;
- // extract functions
+ // Extract load and to_arg functions.
foreach ($parts as $k => $part) {
$match = FALSE;
if (preg_match('/^%([a-z_]*)$/', $part, $matches)) {
@@ -1646,7 +1745,7 @@ function _menu_router_build($callbacks) {
foreach ($menu as $path => $v) {
$item = &$menu[$path];
if (!isset($item['access callback']) && isset($item['access arguments'])) {
- $item['access callback'] = 'user_access'; // Default callback
+ $item['access callback'] = 'user_access'; // Default callback.
}
if (!$item['_tab']) {
// Non-tab items
@@ -1740,6 +1839,9 @@ function _menu_router_build($callbacks) {
return $menu;
}
+/**
+ * Returns TRUE if a path is external (e.g. http://example.com).
+ */
function menu_path_is_external($path) {
$colonpos = strpos($path, ':');
return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
@@ -1749,11 +1851,11 @@ function menu_path_is_external($path) {
* Returns TRUE if the site is off-line for maintenance.
*/
function _menu_site_is_offline() {
- // Check if site is set to off-line mode
+ // Check if site is set to off-line mode.
if (variable_get('site_offline', 0)) {
- // Check if the user has administration privileges
+ // Check if the user has administration privileges.
if (!user_access('administer site configuration')) {
- // Check if this is an attempt to login
+ // Check if this is an attempt to login.
if (drupal_get_normal_path($_GET['q']) != 'user') {
return TRUE;
}
diff --git a/modules/menu/menu.module b/modules/menu/menu.module
index 7f3491ad6..f0dc2e783 100644
--- a/modules/menu/menu.module
+++ b/modules/menu/menu.module
@@ -24,7 +24,7 @@ Menu administration tabs:
return $output;
case 'admin/build/menu':
return '<p>'. t('Menus are a collection of links (menu items) used to navigate a website. The list(s) below display the currently available menus along with their menu items. Select an operation from the list to manage each menu or menu item.', array('@admin-settings-menus' => url('admin/build/menu/settings'), '@admin-block' => url('admin/build/block'))) .'</p>';
- case 'admin/build/menu/menu/add':
+ case 'admin/build/menu/add':
return '<p>'. t('Enter the name for your new menu. Remember to enable the newly created block in the <a href="@blocks">blocks administration page</a>.', array('@blocks' => url('admin/build/block'))) .'</p>';
case 'admin/build/menu/item/add':
return '<p>'. t('Enter the title, path, position and the weight for your new menu item.') .'</p>';
@@ -45,150 +45,169 @@ function menu_menu() {
$items['admin/build/menu'] = array(
'title' => 'Menus',
'description' => "Control your site's navigation menu, primary links and secondary links. as well as rename and reorganize menu items.",
- 'page callback' => 'system_admin_menu_block_page',
- 'file' => 'system.admin.inc',
- 'file path' => drupal_get_path('module', 'system'),
+ 'page callback' => 'menu_overview_page',
'access callback' => 'user_access',
'access arguments' => array('administer menu'),
);
- $result = db_query('SELECT * FROM {menu_custom} ORDER BY title');
- while ($menu = db_fetch_object($result)) {
- $items['admin/build/menu/'. $menu->menu_name] = array(
- 'title' => $menu->title,
- 'page callback' => 'menu_overview',
- 'page arguments' => array($menu->menu_name),
- 'description' => $menu->description,
- );
- $items['admin/build/menu/'. $menu->menu_name .'/list'] = array(
- 'title' => 'List items',
- 'weight' => -10,
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- );
- $items['admin/build/menu/'. $menu->menu_name .'/add'] = array(
- 'title' => 'Add item',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_item', 'add', $menu->menu_name),
- 'type' => MENU_LOCAL_TASK);
- $items['admin/build/menu/'. $menu->menu_name .'/edit'] = array(
- 'title' => 'Edit menu',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_menu', 'edit', $menu->menu_name),
- 'type' => MENU_LOCAL_TASK);
- }
$items['admin/build/menu/list'] = array(
'title' => 'List menus',
'type' => MENU_DEFAULT_LOCAL_TASK,
- 'weight' => -10,
- );
- $items['admin/build/menu/menu/add'] = array(
+ 'weight' => -10);
+ $items['admin/build/menu/add'] = array(
'title' => 'Add menu',
'page callback' => 'drupal_get_form',
'page arguments' => array('menu_edit_menu', 'add'),
'type' => MENU_LOCAL_TASK);
- $items['admin/build/menu/item/disable'] = array(
+ $items['admin/build/menu/settings'] = array(
+ 'title' => 'Settings',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('menu_configure'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 5);
+
+ $items['admin/build/menu-customize/%menu'] = array(
+ 'title' => 'Customize menu',
+ 'page callback' => 'menu_overview',
+ 'page arguments' => array(3),
+ 'access arguments' => array('administer menu'),
+ 'type' => MENU_CALLBACK);
+ $items['admin/build/menu-customize/%menu/list'] = array(
+ 'title' => 'List items',
+ 'weight' => -10,
+ 'type' => MENU_DEFAULT_LOCAL_TASK);
+ $items['admin/build/menu-customize/%menu/add'] = array(
+ 'title' => 'Add item',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('menu_edit_item', 'add', NULL, 3),
+ 'type' => MENU_LOCAL_TASK);
+ $items['admin/build/menu-customize/%menu/edit'] = array(
+ 'title' => 'Edit menu',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('menu_edit_menu', 'edit', 3),
+ 'type' => MENU_LOCAL_TASK);
+ $items['admin/build/menu/item/%menu_link/disable'] = array(
'title' => 'Disable menu item',
'page callback' => 'menu_flip_item',
- 'page arguments' => array(TRUE),
- 'type' => MENU_CALLBACK,
- );
- $items['admin/build/menu/item/enable'] = array(
+ 'page arguments' => array(TRUE, 4),
+ 'type' => MENU_CALLBACK);
+ $items['admin/build/menu/item/%menu_link/enable'] = array(
'title' => 'Enable menu item',
'page callback' => 'menu_flip_item',
- 'page arguments' => array(FALSE),
- 'type' => MENU_CALLBACK,
- );
- $items['admin/build/menu/item/edit'] = array(
+ 'page arguments' => array(FALSE, 4),
+ 'type' => MENU_CALLBACK);
+ $items['admin/build/menu/item/%menu_link/edit'] = array(
'title' => 'Edit menu item',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_item', 'edit', 5),
+ 'page arguments' => array('menu_edit_item', 'edit', 4, NULL),
'type' => MENU_CALLBACK);
- $items['admin/build/menu/item/reset'] = array(
+ $items['admin/build/menu/item/%menu_link/reset'] = array(
'title' => 'Reset menu item',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_reset_item'),
+ 'page arguments' => array('menu_reset_item', 4),
'type' => MENU_CALLBACK);
- $items['admin/build/menu/item/delete'] = array(
+ $items['admin/build/menu/item/%menu_link/delete'] = array(
'title' => 'Delete menu item',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_item_delete_form'),
- 'type' => MENU_CALLBACK);
-
- $items['admin/build/menu/menu/edit'] = array(
- 'title' => 'Edit menu',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_menu', 'edit'),
- 'type' => MENU_CALLBACK);
- $items['admin/build/menu/menu/delete'] = array(
- 'title' => 'Delete menu',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_item_delete_form'),
+ 'page arguments' => array('menu_item_delete_form', 4),
'type' => MENU_CALLBACK);
- $items['admin/build/menu/settings'] = array(
- 'title' => 'Settings',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_configure'),
- 'type' => MENU_LOCAL_TASK,
- 'weight' => 5,
- );
-
return $items;
+}
+/**
+ * Implementation of hook_enable()
+ *
+ * Add a link for each custom menu.
+ */
+function menu_enable() {
+ menu_rebuild();
+ $result = db_query("SELECT * FROM {menu_custom}");
+ $link['module'] = 'menu';
+ $link['plid'] = db_result(db_query("SELECT mlid from {menu_links} WHERE menu_name = 'navigation' AND link_path = 'admin/build/menu'"));
+ $link['router_path'] = 'admin/build/menu-customize/%';
+
+ while ($menu = db_fetch_array($result)) {
+ $link['mlid'] = 0;
+ $link['link_title'] = $menu['title'];
+ $link['link_path'] = 'admin/build/menu-customize/'. $menu['menu_name'];
+ menu_link_save($link);
+ }
}
+/**
+ * Load the data for a single custom menu.
+ */
+function menu_load($menu_name) {
+ return db_fetch_array(db_query("SELECT * FROM {menu_custom} WHERE menu_name = '%s'", $menu_name));
+}
+
+/**
+ * Menu callback which shows an overview page of all the custom menus and their descriptions.
+ */
+function menu_overview_page() {
+ $result = db_query("SELECT * FROM {menu_custom}");
+ $content = array();
+ while ($menu = db_fetch_array($result)) {
+ $menu['href'] = 'admin/build/menu-customize/'. $menu['menu_name'];
+ $menu['options'] = array();
+ $content[] = $menu;
+ }
+ return theme('admin_block_content', $content);
+}
/**
* Menu callback which displays every menu element accessible to the current
* user and the relevant operations.
*/
-function menu_overview($menu_name) {
+function menu_overview($menu) {
+
$header = array(t('Menu item'), t('Expanded'), array('data' => t('Operations'), 'colspan' => '3'));
$sql ="
- SELECT *, ml.weight + 50000 AS weight FROM {menu_links} ml
- LEFT JOIN {menu_router} m ON m.path = ml.router_path
- WHERE menu_name = '%s' AND hidden >= 0
+ SELECT m.*, ml.menu_name, ml.mlid, ml.plid, ml.link_path, ml.router_path, ml.hidden, ml.external, ml.has_children, ml.expanded, ml.weight + 50000 AS weight, ml.depth, ml.customized, ml.module, ml.link_title, ml.options
+ FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+ WHERE ml.menu_name = '%s' AND ml.hidden >= 0
ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC";
$sql_count = "SELECT COUNT(*) FROM {menu_links} ml WHERE menu_name = '%s' AND hidden >= 0";
- $result = pager_query($sql, 200, 0, $sql_count, $menu_name);
- list(, $tree) = _menu_tree_data($result);
+ $result = pager_query($sql, 200, 0, $sql_count, $menu['menu_name']);
+ $tree = menu_tree_data($result);
+ menu_tree_check_access($tree, TRUE);
$rows = _menu_overview_tree($tree);
$output = theme('table', $header, $rows);
$output .= theme('pager', NULL, 200, 0);
return $output;
}
+/**
+ * Recursive helper function for menu_overview().
+ */
function _menu_overview_tree($tree) {
static $rows = array();
foreach ($tree as $data) {
$title = '';
if ($item = $data['link']) {
- _menu_link_translate($item);
- if (!$item['access']) {
- continue;
- }
$title = str_repeat('&nbsp;&nbsp;', $item['depth'] - 1) . ($item['depth'] > 1 ? '-&nbsp;' : '');
$title .= l($item['link_title'], $item['href'], $item['options']);
// Populate the operations field.
$operations = array();
// Set the edit column.
- $operations[] = array('data' => l(t('edit'), 'admin/build/menu/item/edit/'. $item['mlid']));
+ $operations[] = array('data' => l(t('edit'), 'admin/build/menu/item/'. $item['mlid'] .'/edit'));
if ($item['hidden']) {
$title .= ' ('. t('disabled') .')';
$class = 'menu-disabled';
- $operations[] = array('data' => l(t('enable'), 'admin/build/menu/item/enable/'. $item['mlid']));
+ $operations[] = array('data' => l(t('enable'), 'admin/build/menu/item/'. $item['mlid'] .'/enable'));
}
else {
$class = 'menu-enabled';
- $operations[] = array('data' => l(t('disable'), 'admin/build/menu/item/disable/'. $item['mlid']));
+ $operations[] = array('data' => l(t('disable'), 'admin/build/menu/item/'. $item['mlid'] .'/disable'));
}
// Only items created by the menu module can be deleted.
if ($item['module'] == 'menu') {
- $operations[] = array('data' => l(t('delete'), 'admin/build/menu/item/delete/'. $item['mlid']));
+ $operations[] = array('data' => l(t('delete'), 'admin/build/menu/item/'. $item['mlid'] .'/delete'));
}
// Set the reset column.
- else if ($item['module'] == 'system') {
- $operations[] = array('data' => l(t('reset'), 'admin/build/menu/item/reset/'. $item['mlid']));
+ elseif ($item['module'] == 'system' && $item['customized']) {
+ $operations[] = array('data' => l(t('reset'), 'admin/build/menu/item/'. $item['mlid'] .'/reset'));
}
else {
$operations[] = array('data' => '');
@@ -211,55 +230,39 @@ function _menu_overview_tree($tree) {
}
/**
- * Menu callback; enable/disable a menu item.
+ * Menu callback; enable/disable a menu link.
*
* @param $hide
* TRUE to not show in the menu tree. FALSE to make the item and its children
* reappear in menu tree.
- * @param $mlid
- * mlid of the menu item.
+ * @param $item
+ * The menu item.
*/
-function menu_flip_item($hide, $mlid) {
- if (!($item = menu_link_load($mlid))) {
- drupal_not_found();
- return;
- }
+function menu_flip_item($hide, $item) {
+
$item['hidden'] = (bool)$hide;
+ $item['customized'] = 1;
menu_link_save($item);
drupal_set_message($hide ? t('The menu item has been disabled.') : t('The menu item has been enabled.'));
- drupal_goto('admin/build/menu/'. $item['menu_name']);
+ drupal_goto('admin/build/menu-customize/'. $item['menu_name']);
}
/**
- * Menu callback; present the menu item editing form.
+ * Menu callback; Build the menu link editing form.
*/
-function menu_edit_item(&$form_state, $type, $id = 0) {
- if ($type == 'edit') {
- if (!($item = menu_link_load($id))) {
- drupal_not_found();
- return;
- }
- }
- else {
- // This is an add form.
- // The mlid argument (if set) will be the default pid to use.
- $item = array('mlid' => 0, 'plid' => 0, 'menu_name' => $id, 'weight' => 0, 'link_title' => '', 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
+function menu_edit_item(&$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);
}
- foreach (array('link_path', 'mlid', 'module', 'hidden', 'menu_name', 'has_children') as $key) {
+ foreach (array('link_path', 'mlid', 'module', 'hidden', 'menu_name', '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);
- $form['link_title'] = array('#type' => 'textfield',
- '#title' => t('Title'),
- '#default_value' => $item['link_title'],
- '#description' => t('The name of the menu item.'),
- '#required' => TRUE,
- );
- $form['description'] = array(
- '#type' => 'textarea',
- '#title' => t('Description'),
- '#default_value' => isset($item['options']['attributes']['title']) ? $item['options']['attributes']['title'] : '',
- );
if ($item['module'] == 'menu') {
$form['link_path'] = array(
'#type' => 'textfield',
@@ -276,8 +279,19 @@ function menu_edit_item(&$form_state, $type, $id = 0) {
'#description' => l($item['link_title'], $item['href'], $item['options']),
);
}
- $form['original_item'] = array('#type' => 'value', '#value' => $item);
-
+ $form['link_title'] = array('#type' => 'textfield',
+ '#title' => t('Menu link title'),
+ '#default_value' => $item['link_title'],
+ '#description' => t('The link text corresponding to this item that should appear in the menu.'),
+ '#required' => TRUE,
+ );
+ $form['description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Description'),
+ '#default_value' => isset($item['options']['attributes']['title']) ? $item['options']['attributes']['title'] : '',
+ '#rows' => 1,
+ '#description' => t('The description displayed when hovering over a menu item.'),
+ );
$form['expanded'] = array(
'#type' => 'checkbox',
'#title' => t('Expanded'),
@@ -286,7 +300,7 @@ function menu_edit_item(&$form_state, $type, $id = 0) {
);
// Generate a list of possible parents (not including this item or descendants).
- $options = menu_parent_options($item['mlid'], $item['menu_name']);
+ $options = menu_parent_options($item['menu_name'], $item);
$form['plid'] = array(
'#type' => 'select',
'#title' => t('Parent item'),
@@ -296,7 +310,7 @@ function menu_edit_item(&$form_state, $type, $id = 0) {
$form['weight'] = array(
'#type' => 'weight',
'#title' => t('Weight'),
- '#default_value' => isset($item['weight']) ? $item['weight'] : 0,
+ '#default_value' => $item['weight'],
'#description' => t('Optional. In the menu, the heavier items will sink and the lighter items will be positioned nearer the top.'),
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
@@ -304,14 +318,13 @@ function menu_edit_item(&$form_state, $type, $id = 0) {
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'];
- if (isset($item['link_path']) && !menu_path_is_external($item['link_path'])) {
- $path = $item['link_path'];
- $item = menu_get_item($path);
- if (!$item || !$item['access']) {
- form_set_error('path', t('This path is either invalid or you do not have access to it'));
- }
+ if (!trim($item['link_path']) || !menu_valid_path($item)) {
+ 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'])));
}
}
@@ -319,83 +332,56 @@ function menu_edit_item_validate($form, &$form_state) {
* Process menu and menu item add/edit form submissions.
*/
function menu_edit_item_submit($form, &$form_state) {
+ $form_state['values']['options']['attributes']['title'] = $form_state['values']['description'];
menu_link_save($form_state['values']);
- $form_state['redirect'] = 'admin/build/menu/'. $form_state['values']['menu_name'];
+ $form_state['redirect'] = 'admin/build/menu-customize/'. $form_state['values']['menu_name'];
}
/**
* Return a list of menu items that are valid possible parents for the
* given menu item. The list excludes the given item and its children.
*
- * @param $mlid
- * The menu item id for which to generate a list of parents.
- * If $mlid == 0 then the complete tree is returned.
* @param $menu_name
* The name of the menu.
- * @param $plid
- * The menu link item id of the menu item at which to start the tree.
- * If $pid > 0 then this item will be included in the tree.
- * @param $depth
- * The current depth in the tree - used when recursing to indent the tree.
+ * @param $item
+ * The menu item for which to generate a list of parents.
+ * If $item['mlid'] == 0 or NULL then the complete tree is returned.
* @return
- * An array of menu titles keyed on the mlid.
+ * An array of menu link titles keyed on the mlid.
*/
-function menu_parent_options($mlid, $menu_name, $plid = 0, $depth = 0) {
- $options = array(0 => t('Root'));
- // Exclude $mlid and its children from the list unless $mlid is 0.
- if ($mlid && $mlid == $plid) {
- return $options;
- }
+function menu_parent_options($menu_name, $item) {
- $sql = "SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} mr ON ml.router_path = mr.path WHERE menu_name = '%s' AND hidden >= 0";
- $params = array($menu_name);
- if ($mlid && ($item = menu_link_load($mlid))) {
- $parents = array();
- for ($i = 1; $i <= 6; $i++) {
- $key = "p$i";
- $value = $item[$key];
- if ($value) {
- $parents[]= "$key != %d";
- $params[] = $value;
- }
- else {
- break;
- }
- }
- $sql .= ' AND (' . implode(' OR ', $parents) .')';
- }
+ $tree = menu_tree_all_data($item['menu_name'], NULL, TRUE);
+ $options = array(0 => '<'. t('root') .'>');
+ _menu_parents_recurse($tree, '--', $options, $item['mlid']);
- $sql .= ' ORDER BY p1, p2, p3, p4, p5';
- $result = db_query($sql, $params);
-
- while ($item = db_fetch_array($result)) {
- _menu_link_translate($item);
- if (!$item['access']) {
- continue;
- }
- $title = str_repeat('--', $item['depth']) .' '. $item['link_title'];
- if ($item['hidden']) {
- $title .= ' ('. t('disabled') .')';
- }
- $options[$item['mlid']] = $title;
- }
return $options;
}
/**
- * Remove the menu item.
+ * Recursive helper function for menu_parent_options().
*/
-function menu_node_form_delete($node) {
- menu_link_delete(NULL, 'node/'. $node->nid);
+function _menu_parents_recurse($tree, $indent, &$options, $exclude) {
+ foreach ($tree as $data) {
+ if ($data['link']['mlid'] != $exclude) {
+ $title = $indent .' '. truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
+ if ($data['link']['hidden']) {
+ $title .= ' ('. t('disabled') .')';
+ }
+ $options[$data['link']['mlid']] = $title;
+ if ($data['below'] && $data['link']['depth'] < MENU_MAX_DEPTH - 1) {
+ _menu_parents_recurse($data['below'], $indent .'--', $options, $exclude);
+ }
+ }
+ }
}
/**
- * Menu callback; handle the adding/editing of a new menu.
+ * Menu callback; Build the form that handles the adding/editing of a custom menu.
*/
-function menu_edit_menu(&$form_state, $type, $menu_name = '') {
+function menu_edit_menu(&$form_state, $type, $menu = array()) {
if ($type == 'edit') {
- $menu = db_fetch_array(db_query("SELECT * FROM {menu_custom} WHERE menu_name = '%s'", $menu_name));
- $form['menu_name'] = array('#type' => 'value', '#value' => $menu_name);
+ $form['menu_name'] = array('#type' => 'value', '#value' => $menu['menu_name']);
$form['#insert'] = FALSE;
}
else {
@@ -403,7 +389,7 @@ function menu_edit_menu(&$form_state, $type, $menu_name = '') {
$form['menu_name'] = array(
'#type' => 'textfield',
'#title' => t('Menu name'),
- '#description' => t('The machine-readable name of this menu. This text will be used for constructing the URL of the <em>menu overwrite</em> page for this menu. This name may consist of only of lowercase letters, numbers and hyphens and must be unique.'),
+ '#description' => t('The machine-readable name of this menu. This text will be used for constructing the URL of the <em>menu overview</em> page for this menu. This name may consist of only of lowercase letters, numbers and hypens and must be unique.'),
'#required' => TRUE,
);
$form['#insert'] = TRUE;
@@ -428,10 +414,13 @@ function menu_edit_menu(&$form_state, $type, $menu_name = '') {
return $form;
}
+/**
+ * Validates the human and machine-readable names when adding or editing a menu.
+ */
function menu_edit_menu_validate($form, &$form_state) {
$item = $form_state['values'];
if (preg_match('/[^a-z0-9-]/', $item['menu_name'])) {
- form_set_error('menu_name', t('Menu name may consist of only of lowercase letters, numbers and hyphens.'));
+ form_set_error('menu_name', t('Menu name may consist only of lowercase letters, numbers and hypens.'));
}
if ($form['#insert'] &&
(db_result(db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = '%s'", $item['menu_name'])) ||
@@ -440,31 +429,44 @@ function menu_edit_menu_validate($form, &$form_state) {
}
}
+/**
+ * Submit function for adding or editing a custom menu.
+ */
function menu_edit_menu_submit($form, &$form_state) {
$menu = $form_state['values'];
- $redirect = 'admin/build/menu/'. $menu['menu_name'];
+ // Append 'menu' to the menu name to help avoid name-space conflicts.
+ $menu['menu_name'] = 'menu-'. $menu['menu_name'];
+ $redirect = 'admin/build/menu-customize/'. $menu['menu_name'];
+ $link['link_title'] = $menu['title'];
+ $link['router_path'] = 'admin/build/menu-customize/%';
+ $link['module'] = 'menu';
+ $link['link_path'] = $redirect;
if ($form['#insert']) {
+ $link['plid'] = db_result(db_query("SELECT mlid from {menu_links} WHERE menu_name = 'navigation' AND link_path = 'admin/build/menu'"));
+ menu_link_save($link);
db_query("INSERT INTO {menu_custom} (menu_name, title, description) VALUES ('%s', '%s', '%s')", $menu['menu_name'], $menu['title'], $menu['description']);
}
else {
db_query("UPDATE {menu_custom} SET title = '%s', description = '%s' WHERE menu_name = '%s'", $menu['title'], $menu['description'], $menu['menu_name']);
- db_query("UPDATE {menu_links} SET link_title = '%s' WHERE link_title = '%s' AND router_path = '%s'", $menu['title'], $form['#title'], $redirect);
+ $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = '%s'", $link['link_path']);
+ while ($m = db_fetch_array($result)) {
+ $link = menu_link_load($m['mlid']);
+ $link['link_title'] = $menu['title'];
+ menu_link_save($link);
+ }
}
- menu_rebuild();
$form_state['redirect'] = $redirect;
}
/**
- * Menu callback; delete a single custom item.
+ * Menu callback; Build a confirm form for deletion of a single menu link.
*/
-function menu_item_delete_form(&$form_state, $mlid) {
- if (!$item = menu_link_load($mlid)) {
- drupal_not_found();
- return;
+function menu_item_delete_form(&$form_state, $item) {
+ if ($item['module'] == 'system') {
+ drupal_access_denied();
}
$form['#item'] = $item;
-
- return confirm_form($form, t('Are you sure you want to delete the custom menu item %item?', array('%item' => $item['link_title'])), 'admin/build/menu/'. $item['menu_name'], t('This action cannot be undone.'), t('Delete'));
+ return confirm_form($form, t('Are you sure you want to delete the custom menu item %item?', array('%item' => $item['link_title'])), 'admin/build/menu-customize/'. $item['menu_name']);
}
/**
@@ -476,40 +478,37 @@ function menu_item_delete_form_submit($form, &$form_state) {
$t_args = array('%title' => $item['link_title']);
drupal_set_message(t('The menu item %title has been deleted.', $t_args));
watchdog('menu', 'Deleted menu item %title.', $t_args, WATCHDOG_NOTICE);
- $form_state['redirect'] = 'admin/build/menu/'. $item['menu_name'];
+ $form_state['redirect'] = 'admin/build/menu-customize/'. $item['menu_name'];
}
/**
* Menu callback; reset a single modified item.
*/
-function menu_reset_item(&$form_state, $mlid) {
- if (isset($mlid) && $item = db_fetch_array(db_query('SELECT router_path, link_title FROM {menu_links} WHERE mlid = %d', $mlid))) {
- $form['#router_path'] = $item['router_path'];
-
- $options = array(
- 'description' => t('Any customizations will be lost. This action cannot be undone.'),
- 'yes' => t('Reset')
- );
+function menu_reset_item(&$form_state, $item) {
+ $form['item'] = array('#type' => 'value', '#value' => $item);
- return confirm_form($form, t('Are you sure you want to reset the item %item to its default values?', array('%item' => $item['link_title'])), 'admin/build/menu', $options);
- }
- else {
- drupal_not_found();
- }
+ $options = array(
+ 'description' => t('Any customizations will be lost. This action cannot be undone.'),
+ 'yes' => t('Reset')
+ );
+ return confirm_form($form, t('Are you sure you want to reset the item %item to its default values?', array('%item' => $item['link_title'])), 'admin/build/menu-customize/'. $item['menu_name'], $options);
}
/**
* Process menu reset item form submissions.
*/
function menu_reset_item_submit($form, &$form_state) {
- $new_item = db_fetch_array(db_query("SELECT * FROM {menu_router} WHERE path = '%s'", $form['#router_path']));
- menu_link_save(_menu_link_build($new_item));
+ $item = $form_state['values']['item'];
+ $router = menu_router_build();
+ $new_item = _menu_link_build($router[$item['router_path']]);
+ foreach (array('mlid', 'has_children') as $key) {
+ $new_item[$key] = $item[$key];
+ }
+ menu_link_save($new_item);
drupal_set_message(t('The menu item was reset to its default settings.'));
- $form_state['redirect'] = 'admin/build/menu/navigation';
+ $form_state['redirect'] = 'admin/build/menu-customize/'. $new_item['menu_name'];
}
-// Conversion ends here.
-
/**
* Implementation of hook_block().
*/
@@ -533,24 +532,47 @@ function menu_block($op = 'list', $delta = 0) {
/**
* Implementation of hook_nodeapi().
*/
-function menu_nodeapi($node, $op) {
- if (user_access('administer menu') && isset($node->menu)) {
- $item = $node->menu;
- switch ($op) {
- case 'delete':
- $item['delete'] = 1;
- // Deliberate no break.
- case 'insert':
- case 'update':
- if ($item['delete']) {
- _menu_delete_item($item);
+function menu_nodeapi(&$node, $op) {
+ switch ($op) {
+ case 'insert':
+ case 'update':
+ if (isset($node->menu)) {
+ $item = $node->menu;
+ if (!empty($item['delete'])) {
+ menu_link_delete($item['mlid']);
}
- else {
+ elseif (trim($item['link_title'])) {
+ $item['link_title'] = trim($item['link_title']);
$item['link_path'] = "node/$node->nid";
+ if (!$item['customized']) {
+ $item['options']['attributes']['title'] = trim($node->title);
+ }
menu_link_save($item);
}
- break;
- }
+ }
+ break;
+ case 'delete':
+ // Delete all menu module links that point to this node.
+ $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND module = 'menu'", $node->nid);
+ while ($m = db_fetch_array($result)) {
+ menu_link_delete($m['mlid']);
+ }
+ break;
+ case 'prepare':
+ if (empty($node->menu)) {
+ // Prepare the node for the edit form so that $node->menu always exists.
+ $menu_name = variable_get('menu_parent_items', 'navigation');
+ $item = array();
+ if (isset($node->nid)) {
+ $mlid = db_result(db_query("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND menu_name = '%s' AND module = 'menu' ORDER BY mlid ASC", $node->nid, $menu_name));
+ if ($mlid) {
+ $item = menu_link_load($mlid);
+ }
+ }
+ // 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);
+ }
+ break;
}
}
@@ -559,7 +581,10 @@ function menu_nodeapi($node, $op) {
* Add menu item fields to the node form.
*/
function menu_form_alter(&$form, $form_state, $form_id) {
- if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
+ if (isset($form['#node']) && $form['#node']->type .'_node_form' == $form_id) {
+ // Note - doing this to make sure the delete checkbox stays in the form.
+ $form['#cache'] = TRUE;
+
$form['menu'] = array(
'#type' => 'fieldset',
'#title' => t('Menu settings'),
@@ -567,27 +592,58 @@ function menu_form_alter(&$form, $form_state, $form_id) {
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#tree' => TRUE,
- '#weight' => 30,
+ '#weight' => -2,
);
- $form_state = array();
- if ($mlid = db_result(db_query("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND menu_name = '%s'", $form['nid']['#value'], variable_get('menu_parent_items', 'navigation')))) {
- $menu_form = drupal_retrieve_form('menu_edit_item', $form_state, 'edit', $mlid);
- $menu_form['delete'] = array(
+ $item = $form['#node']->menu;
+
+ if ($item['mlid']) {
+ // There is an existing link
+ $form['menu']['delete'] = array(
'#type' => 'checkbox',
- '#title' => t('Check to delete this menu item.'),
+ '#title' => t('Check to remove this item from the menu.'),
);
}
- else {
- $menu_form = drupal_retrieve_form('menu_edit_item', $form_state, 'add', variable_get('menu_parent_items', 'navigation'));
- unset($menu_form['link_path']);
- $menu_form['link_title']['#required'] = FALSE;
+ if (!$item['link_title']) {
$form['menu']['#collapsed'] = TRUE;
}
- unset($menu_form['submit']);
- $form['menu'] += $menu_form;
+
+ foreach (array('mlid', 'module', 'hidden', 'menu_name', 'has_children', 'customized', 'options', 'expanded', 'hidden') as $key) {
+ $form['menu'][$key] = array('#type' => 'value', '#value' => $item[$key]);
+ }
+
+ $form['menu']['link_title'] = array('#type' => 'textfield',
+ '#title' => t('Menu link title'),
+ '#default_value' => $item['link_title'],
+ '#description' => t('The link text corresponding to this item that should appear in the menu. Leave blank if you do not wish to add this post to the menu.'),
+ '#required' => FALSE,
+ );
+ // Generate a list of possible parents (not including this item or descendants).
+ $options = menu_parent_options($item['menu_name'], $item);
+ $form['menu']['plid'] = array(
+ '#type' => 'select',
+ '#title' => t('Parent item'),
+ '#default_value' => $item['plid'],
+ '#options' => $options,
+ );
+ $form['menu']['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight'),
+ '#default_value' => $item['weight'],
+ '#description' => t('Optional. In the menu, the heavier items will sink and the lighter items will be positioned nearer the top.'),
+ );
}
}
+/**
+ * Return an associative array of the custom menus names.
+ *
+ * @param $all
+ * If FALSE return only user-added menus, or if TRUE also include
+ * the menus defined by the system.
+ * @return
+ * An array with the machine-readable names as the keys, and human-readable
+ * titles as the values.
+ */
function menu_get_menus($all = FALSE) {
$sql = 'SELECT * FROM {menu_custom}'. ($all ? '' : " WHERE menu_name NOT IN ('navigation', 'primary-links', 'secondary-links')") . ' ORDER BY title';
$result = db_query($sql);
@@ -600,7 +656,7 @@ function menu_get_menus($all = FALSE) {
}
/**
- * Menu callback; presents menu configuration options.
+ * Menu callback; Build the form presenting menu configuration options.
*/
function menu_configure() {
$form['intro'] = array(
@@ -619,3 +675,32 @@ function menu_configure() {
return system_settings_form($form);
}
+
+/**
+ * Validates the path of a menu link being created or edited.
+ *
+ * @return
+ * TRUE if it is a valid path AND the current user has access permission,
+ * FALSE otherwise.
+ */
+function menu_valid_path($form_item) {
+ $item = array();
+ $path = $form_item['link_path'];
+ if ($path == '<front>' || menu_path_is_external($path)) {
+ $item = array('access' => TRUE);
+ }
+ elseif (preg_match('/\/\%/', $path)) {
+ // Path is dynamic (ie 'user/%'), so check directly against menu_router table.
+ if ($item = db_fetch_array(db_query("SELECT * FROM {menu_router} where path = '%s' ", $path))) {
+ $item['link_path'] = $form_item['link_path'];
+ $item['link_title'] = $form_item['link_title'];
+ $item['external'] = FALSE;
+ $item['options'] = '';
+ _menu_link_translate($item);
+ }
+ }
+ else {
+ $item = menu_get_item($path);
+ }
+ return $item && $item['access'];
+}
diff --git a/modules/system/system.install b/modules/system/system.install
index 7aa12ff8b..525d66ceb 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -3339,6 +3339,7 @@ function system_update_6020() {
'expanded' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
'depth' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+ 'customized' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
'p1' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'p2' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'p3' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
diff --git a/modules/system/system.schema b/modules/system/system.schema
index a1ea98b8c..544b4f1b2 100644
--- a/modules/system/system.schema
+++ b/modules/system/system.schema
@@ -110,6 +110,7 @@ function system_schema() {
'expanded' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
'depth' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
+ 'customized' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'small'),
'p1' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'p2' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
'p3' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
diff --git a/profiles/default/default.profile b/profiles/default/default.profile
index 7a2a6b140..518b377c1 100644
--- a/profiles/default/default.profile
+++ b/profiles/default/default.profile
@@ -8,7 +8,7 @@
* An array of modules to be enabled.
*/
function default_profile_modules() {
- return array('color', 'comment', 'help', 'taxonomy', 'dblog');
+ return array('color', 'comment', 'help', 'menu', 'taxonomy', 'dblog');
}
/**