From e0fe1ae8eb5dd4e5d5442b8b48afe2c47a166d2b Mon Sep 17 00:00:00 2001 From: Dries Buytaert <dries@buytaert.net> Date: Wed, 4 Jul 2007 15:49:44 +0000 Subject: [PATCH] - Patch #151583 by pwolanin, Shakur, webernet, Arancaytar et al: various menu module fixes. --- includes/menu.inc | 242 ++++++++++---- modules/menu/menu.module | 557 ++++++++++++++++++------------- modules/system/system.install | 1 + modules/system/system.schema | 1 + profiles/default/default.profile | 2 +- 5 files changed, 496 insertions(+), 307 deletions(-) diff --git a/includes/menu.inc b/includes/menu.inc index f96641273a99..ec3e2e93f233 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 7f3491ad6b1b..f0dc2e783a06 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -24,7 +24,7 @@ function menu_help($path, $arg) { 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(' ', $item['depth'] - 1) . ($item['depth'] > 1 ? '- ' : ''); $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 7aa12ff8b1d3..525d66ceb49a 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 a1ea98b8c408..544b4f1b2a97 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 7a2a6b14046d..518b377c15c4 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'); } /** -- GitLab