=== modified file 'includes/menu.inc' --- includes/menu.inc 2007-04-13 08:56:57 +0000 +++ includes/menu.inc 2007-04-14 00:27:32 +0000 @@ -307,6 +307,7 @@ function menu_get_item($path = NULL, $it $original_map = arg(NULL, $path); $parts = array_slice($original_map, 0, 6); list($ancestors, $placeholders) = menu_get_ancestors($parts); + $item->active_trail = array(); if ($item = db_fetch_object(db_query_range('SELECT * FROM {menu} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) { // We need to access check the parents to match the navigation tree // behaviour. The last parent is always the item itself. @@ -320,8 +321,12 @@ function menu_get_item($path = NULL, $it $items[$path] = FALSE; return FALSE; } - $item->access = $item->access && $parent->access; - $item->active_trail[] = $parent; + if ($parent->access) { + $item->active_trail[] = $parent; + } + else { + $item->access = FALSE; + } } if ($item->access) { $item->map = $map; @@ -437,8 +442,15 @@ function _menu_translate(&$item, $map, $ */ function menu_tree() { if ($item = menu_get_item()) { - $args = explode(',', $item->parents); - $placeholders = implode(', ', array_fill(0, count($args), '%d')); + if ($item->access) { + $args = explode(',', $item->parents); + $placeholders = implode(', ', array_fill(0, count($args), '%d')); + } + // Show the root menu for access denied. + else { + $args = 0; + $placeholders = '%d'; + } list(, $menu) = _menu_tree(db_query('SELECT * FROM {menu} WHERE pid IN ('. $placeholders .') AND visible = 1 ORDER BY mleft', $args)); return $menu; } @@ -548,7 +560,7 @@ function menu_get_active_help() { $output = ''; $item = menu_get_item(); - if (!$item->access) { + if (!$item || !$item->access) { // Don't return help text for areas the user cannot access. return; } @@ -570,66 +582,80 @@ function menu_get_active_help() { return $output; } +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); +} + /** * Populate the database representation of the menu. */ function menu_rebuild() { // TODO: split menu and menu links storage. - db_query('DELETE FROM {menu}'); $menu = module_invoke_all('menu'); // Alter the menu as defined in modules, keys are like user/%user. drupal_alter('menu', $menu, MENU_ALTER_MODULE_DEFINED); + db_query('DELETE FROM {menu}'); $mid = 1; // First pass: separate callbacks from pathes, making pathes ready for // matching. Calculate fitness, and fill some default values. foreach ($menu as $path => $item) { - $parts = explode('/', $path, 6); - $number_parts = count($parts); - // We store the highest index of parts here to save some work in the fit - // calculation loop. - $slashes = $number_parts - 1; - $fit = 0; $load_functions = array(); $to_arg_functions = array(); - // extract functions - foreach ($parts as $k => $part) { - $match = FALSE; - if (preg_match('/^%([a-z_]*)$/', $part, $matches)) { - if (empty($matches[1])) { - $match = TRUE; - $load_functions[$k] = NULL; - } - else { - if (function_exists($matches[1] .'_to_arg')) { - $to_arg_functions[$k] = $matches[1] .'_to_arg'; - $load_functions[$k] = NULL; + $fit = 0; + $move = FALSE; + if (!isset($item['_external'])) { + $item['_external'] = menu_path_is_external($path); + } + if ($item['_external']) { + $number_parts = 0; + $parts = array(); + } + else { + $parts = explode('/', $path, 6); + $number_parts = count($parts); + // We store the highest index of parts here to save some work in the fit + // calculation loop. + $slashes = $number_parts - 1; + // extract functions + foreach ($parts as $k => $part) { + $match = FALSE; + if (preg_match('/^%([a-z_]*)$/', $part, $matches)) { + if (empty($matches[1])) { $match = TRUE; + $load_functions[$k] = NULL; } - if (function_exists($matches[1] .'_load')) { - $load_functions[$k] = $matches[1] .'_load'; - $match = TRUE; + else { + if (function_exists($matches[1] .'_to_arg')) { + $to_arg_functions[$k] = $matches[1] .'_to_arg'; + $load_functions[$k] = NULL; + $match = TRUE; + } + if (function_exists($matches[1] .'_load')) { + $load_functions[$k] = $matches[1] .'_load'; + $match = TRUE; + } } } + if ($match) { + $parts[$k] = '%'; + } + else { + $fit |= 1 << ($slashes - $k); + } } - if ($match) { - $parts[$k] = '%'; + if ($fit) { + $move = TRUE; } else { - $fit |= 1 << ($slashes - $k); + // If there is no %, it fits maximally. + $fit = (1 << $number_parts) - 1; } } $item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions); $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions); - // If there is no %, it fits maximally. - if (!$fit) { - $fit = (1 << $number_parts) - 1; - $move = FALSE; - } - else { - $move = TRUE; - } $item += array( 'title' => '', 'weight' => 0, @@ -662,29 +688,31 @@ function menu_rebuild() { // Second pass: prepare for sorting and find parents. foreach ($menu as $path => $item) { $item = &$menu[$path]; - $number_parts = $item['_number_parts']; - if (isset($item['parent'])) { - if (isset($menu_path_map[$item['parent']])) { - $item['parent'] = $menu_path_map[$item['parent']]; - } - $parent_parts = explode('/', $item['parent'], 6); - $slashes = count($parent_parts); - } - else { - $parent_parts = $item['_parts']; - $slashes = $number_parts - 1; - } - $depth = 1; + $parent_path = $path; $parents = array($item['_mid']); - for ($i = $slashes; $i; $i--) { - $parent_path = implode('/', array_slice($parent_parts, 0, $i)); - if (isset($menu[$parent_path]) && $menu[$parent_path]['_visible']) { - $parent = $menu[$parent_path]; - $parents[] = $parent['_mid']; - $depth++; - if (!isset($item['_pid'])) { - $item['_pid'] = $parent['_mid']; - $item['_visible_parent_path'] = $parent_path; + $depth = 1; + if (isset($item['parent']) && isset($menu_path_map[$item['parent']])) { + $item['parent'] = $menu_path_map[$item['parent']]; + } + if ($item['_visible'] || $item['_tab']) { + while ($parent_path) { + if (isset($menu[$parent_path]['parent'])) { + if (isset($menu_path_map[$menu[$parent_path]['parent']])) { + $menu[$parent_path]['parent'] = $menu_path_map[$menu[$parent_path]['parent']]; + } + $parent_path = $menu[$parent_path]['parent']; + } + else { + $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); + } + if (isset($menu[$parent_path]) && $menu[$parent_path]['_visible']) { + $parent = $menu[$parent_path]; + $parents[] = $parent['_mid']; + $depth++; + if (!isset($item['_pid'])) { + $item['_pid'] = $parent['_mid']; + $item['_visible_parent_path'] = $parent_path; + } } } } @@ -693,9 +721,8 @@ function menu_rebuild() { // Store variables and set defaults. $item += array( '_pid' => 0, - '_depth' => ($item['_visible'] ? $depth : $number_parts), + '_depth' => ($item['_visible'] ? $depth : $item['_number_parts']), '_parents' => $parents, - '_parent_parts' => $parent_parts, '_slashes' => $slashes, ); // This sorting works correctly only with positive numbers, @@ -708,7 +735,7 @@ function menu_rebuild() { // We are now sorted, so let's build the tree. $children = array(); foreach ($menu as $path => $item) { - if ($item['_pid']) { + if (!empty($item['_pid'])) { $menu[$item['_visible_parent_path']]['_children'][] = $path; } } @@ -716,54 +743,60 @@ function menu_rebuild() { // Apply inheritance rules. foreach ($menu as $path => $item) { - $item = &$menu[$path]; - for ($i = $item['_number_parts'] - 1; $i; $i--) { - $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); - if (isset($menu[$parent_path])) { - $parent = $menu[$parent_path]; - // If a callback is not found, we try to find the first parent that - // has this callback. When found, its callback argument will also be - // copied but only if there is none in the current item. - - // Because access is checked for each parent as well, we only inherit - // if arguments were given without a callback. Otherwise the inherited - // check would be identical to that of the parent. - if (!isset($item['access callback']) && isset($parent['access callback']) && !isset($parent['access inherited'])) { - if (isset($item['access arguments'])) { - $item['access callback'] = $parent['access callback']; - } - else { - $item['access callback'] = 1; - // If a children of this element has an argument, we need to pair - // that with a real callback, not the 1 we set above. - $item['access inherited'] = TRUE; + if ($item['_external']) { + $item['access callback'] = 1; + } + else { + $item = &$menu[$path]; + for ($i = $item['_number_parts'] - 1; $i; $i--) { + $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); + if (isset($menu[$parent_path])) { + $parent = $menu[$parent_path]; + // If a callback is not found, we try to find the first parent that + // has this callback. When found, its callback argument will also be + // copied but only if there is none in the current item. + + // Because access is checked for each visible parent as well, we only + // inherit if arguments were given without a callback. Otherwise the + // inherited check would be identical to that of the parent. + if (!isset($item['access callback']) && isset($parent['access callback']) && !isset($parent['access inherited'])) { + if (isset($item['access arguments']) || !$parent['_visible']) { + $item['access callback'] = $parent['access callback']; + } + else { + $item['access callback'] = 1; + // If a children of this element has an argument, we need to pair + // that with a real callback, not the 1 we set above. + $item['access inherited'] = TRUE; + } } - } - // Unlike access callbacks, there are no shortcuts for page callbacks. - if (!isset($item['page callback']) && isset($parent['page callback'])) { - $item['page callback'] = $parent['page callback']; - if (!isset($item['page arguments']) && isset($parent['page arguments'])) { - $item['page arguments'] = $parent['page arguments']; + // Unlike access callbacks, there are no shortcuts for page callbacks. + if (!isset($item['page callback']) && isset($parent['page callback'])) { + $item['page callback'] = $parent['page callback']; + if (!isset($item['page arguments']) && isset($parent['page arguments'])) { + $item['page arguments'] = $parent['page arguments']; + } } } } + if (!isset($item['access callback'])) { + $item['access callback'] = isset($item['access arguments']) ? 'user_access' : 0; + } + if (is_bool($item['access callback'])) { + $item['access callback'] = intval($item['access callback']); + } + if (empty($item['page callback'])) { + $item['access callback'] = 0; + } } - if (!isset($item['access callback'])) { - $item['access callback'] = isset($item['access arguments']) ? 'user_access' : 0; - } - if (is_bool($item['access callback'])) { - $item['access callback'] = intval($item['access callback']); - } - if (empty($item['page callback'])) { - $item['access callback'] = 0; - } + if ($item['_tab']) { - if (!isset($item['parent'])) { - $item['parent'] = implode('/', array_slice($item['_parts'], 0, $item['_number_parts'] - 1)); + if (isset($item['parent'])) { + $item['_depth'] = $item['parent'] ? $menu[$item['parent']]['_depth'] + 1 : 1; } else { - $item['_depth'] = $item['parent'] ? $menu[$item['parent']]['_depth'] + 1 : 1; + $item['parent'] = implode('/', array_slice($item['_parts'], 0, $item['_number_parts'] - 1)); } } else { @@ -771,6 +804,7 @@ function menu_rebuild() { // stored in parents, parent stores the tab parent. $item['parent'] = $path; } + $insert_item = $item; unset($item); $item = $insert_item + array( @@ -871,6 +905,9 @@ function menu_local_tasks($level = 0) { static $tabs = array(), $parents = array(), $parents_done = array(); if (empty($tabs)) { $router_item = menu_get_item(); + if (!$router_item || !$router_item->access) { + return array(); + } $map = arg(NULL); do { // Tabs are router items that have the same parent. If there is a new @@ -943,8 +980,10 @@ function menu_set_location() { function menu_get_active_breadcrumb() { $breadcrumb = array(l(t('Home'), '')); $item = menu_get_item(); - foreach ($item->active_trail as $parent) { - $breadcrumb[] = l($parent->title, $parent->link_path, (array)$parent); + if ($item && $item->access) { + foreach ($item->active_trail as $parent) { + $breadcrumb[] = l($parent->title, $parent->link_path, (array)$parent); + } } return $breadcrumb; } === modified file 'modules/menu/menu.install' --- modules/menu/menu.install 2007-04-06 05:29:22 +0000 +++ modules/menu/menu.install 2007-04-13 01:55:49 +0000 @@ -1,5 +1,5 @@ 'drupal_get_form', 'page arguments' => array('menu_item_delete_form'), 'type' => MENU_CALLBACK); - $result = db_query('SELECT * FROM {menu_edit} WHERE admin = 1'); + $result = db_query('SELECT * FROM {menu_custom} WHERE admin = 1'); while ($item = db_fetch_array($result)) { - $item['access callback'] = 1; - $item['access inherited'] = TRUE; + $item['_custom_item'] = TRUE; + $item['_external'] = menu_path_is_external($item['path']); $items[$item['path']] = $item; } + return $items; /* $items[] = array('path' => 'admin/build/menu/menu/add', @@ -128,11 +129,29 @@ function menu_menu() { * Implementation of hook_menu_alter. */ function menu_menu_alter(&$menu, $phase) { - if ($phase == MENU_ALTER_PREPROCESSED) { - $result = db_query('SELECT * FROM {menu_edit} me WHERE admin = 0'); - while ($item = db_fetch_array($result)) { - $menu[$item['path']] = $item + $menu[$item['path']]; - } + switch ($phase) { + case MENU_ALTER_MODULE_DEFINED: + foreach ($menu as $path => $item) { + if (isset($item['_custom_item']) && $item['_custom_item'] && !$item['_external']) { + list($ancestors, $placeholders) = menu_get_ancestors(explode('/', $path, 6)); + // Remove the item itself, custom items need to inherit from an existing item. + array_shift($ancestors); + array_shift($placeholders); + $inherit_item = db_fetch_object(db_query_range('SELECT * FROM {menu} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1)); + drupal_set_message(var_export($inherit_item, TRUE)); + $menu[$path]['access callback'] = $inherit_item->access_callback; + $menu[$path]['access arguments'] = unserialize($inherit_item->access_arguments); + $menu[$path]['page callback'] = $inherit_item->page_callback; + $menu[$path]['page arguments'] = unserialize($inherit_item->page_arguments); + } + } + break; + case MENU_ALTER_PREPROCESSED: + $result = db_query('SELECT * FROM {menu_custom} me WHERE admin = 0'); + while ($item = db_fetch_array($result)) { + $menu[$item['path']] = $item + $menu[$item['path']]; + } + break; } } @@ -142,7 +161,7 @@ function menu_menu_alter(&$menu, $phase) */ function menu_overview() { $header = array(t('Menu item'), t('Expanded'), array('data' => t('Operations'), 'colspan' => '3')); - $result = db_query('SELECT m.*, me.disabled FROM {menu} m LEFT JOIN {menu_edit} me ON m.path = me.path WHERE visible = 1 OR (disabled = 1 AND admin = 0) ORDER BY mleft'); + $result = db_query('SELECT m.*, me.disabled FROM {menu} m LEFT JOIN {menu_custom} me ON m.path = me.path WHERE visible = 1 OR (disabled = 1 AND admin = 0) ORDER BY mleft'); $map = arg(); $rows = array(); while ($item = db_fetch_object($result)) { @@ -201,17 +220,17 @@ function menu_overview() { */ function menu_flip_item($visible, $mid, $path = NULL) { if (isset($mid)) { - $parent = menu_get_item_by_mid($mid); + $item = menu_get_item_by_mid($mid); } elseif (isset($path)) { - $parent = menu_get_item($path); + $item = menu_get_item($path); } - if (isset($parent) && $parent->access) { - $result = db_query('SELECT * FROM {menu} WHERE %d <= mleft AND mright <= %d', $parent->mleft, $parent->mright); + if (isset($item) && $item->access) { + $result = db_query('SELECT child.*, parent.path AS parent_path FROM {menu} child INNER JOIN {menu} parent ON child.pid = parent.mid WHERE %d <= child.mleft AND child.mright <= %d', $item->mleft, $item->mright); while ($item = db_fetch_object($result)) { - $update_result = db_query("UPDATE {menu_edit} SET disabled = %d WHERE path = '%s'", !$visible, $item->path); + $update_result = db_query("UPDATE {menu_custom} SET disabled = %d WHERE path = '%s'", !$visible, $item->path); if (!db_affected_rows($update_result)) { - db_query("INSERT INTO {menu_edit} (parent, path, title, description, weight, type, admin, disabled) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d, %d)", $item->parent, $item->path, $item->title, $item->description, 0, $item->type, 0, !$visible); + db_query("INSERT INTO {menu_custom} (parent, path, title, description, weight, type, admin, disabled) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d, %d)", $item->parent_path, $item->path, $item->title, $item->description, 0, $item->type, 0, !$visible); } } menu_rebuild(); @@ -348,7 +367,7 @@ function menu_parent_options($mid, $pid if ($mid && $mid == $pid) { return $options; } - $sql = 'SELECT m.*, me.disabled FROM {menu} m LEFT JOIN {menu_edit} me ON m.path = me.path WHERE (m.visible = 1 OR (me.disabled = 1 AND me.admin = 0))'; + $sql = 'SELECT m.*, me.disabled FROM {menu} m LEFT JOIN {menu_custom} me ON m.path = me.path WHERE (m.visible = 1 OR (me.disabled = 1 AND me.admin = 0))'; if (!$mid) { $params = array(); } @@ -398,11 +417,11 @@ function menu_edit_item_save($edit) { $parent = $edit['pid'] ? db_result(db_query('SELECT path FROM {menu} WHERE mid = %d', $edit['pid'])) : ''; $t_args = array('%title' => $edit['title']); - if (!empty($edit['original_path']) && db_num_rows(db_query("SELECT * FROM {menu_edit} WHERE path='%s'", $edit['original_path']))) { - db_query("UPDATE {menu_edit} SET parent = '%s', title = '%s', description = '%s', weight = %d, type = %d, path = '%s' WHERE path = '%s'", $parent, $edit['title'], $edit['description'], $edit['weight'], $edit['type'], isset($edit['path']) ? $edit['path'] : $edit['original_path'], $edit['original_path']); + if (!empty($edit['original_path']) && db_num_rows(db_query("SELECT * FROM {menu_custom} WHERE path='%s'", $edit['original_path']))) { + db_query("UPDATE {menu_custom} SET parent = '%s', title = '%s', description = '%s', weight = %d, type = %d, path = '%s' WHERE path = '%s'", $parent, $edit['title'], $edit['description'], $edit['weight'], $edit['type'], isset($edit['path']) ? $edit['path'] : $edit['original_path'], $edit['original_path']); } else { - db_query("INSERT INTO {menu_edit} (parent, path, title, description, weight, type, admin) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $parent, isset($edit['path']) ? $edit['path'] : $edit['original_path'], $edit['title'], $edit['description'], $edit['weight'], $edit['type'], isset($edit['path'])); + db_query("INSERT INTO {menu_custom} (parent, path, title, description, weight, type, admin) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $parent, isset($edit['path']) ? $edit['path'] : $edit['original_path'], $edit['title'], $edit['description'], $edit['weight'], $edit['type'], isset($edit['path'])); } watchdog('menu', t('Saved menu item %title.', $t_args), WATCHDOG_NOTICE, l(t('view'), 'admin/build/menu')); drupal_set_message(t('The menu item %title has been saved.', $t_args)); @@ -522,7 +541,7 @@ function menu_reset_item_submit($form_id * The path to the menu item to be deleted. */ function menu_delete_item($path) { - db_query("DELETE FROM {menu_edit} WHERE path = '%s'", $path); + db_query("DELETE FROM {menu_custom} WHERE path = '%s'", $path); menu_rebuild(); } === modified file 'modules/system/system.install' --- modules/system/system.install 2007-04-13 08:53:24 +0000 +++ modules/system/system.install 2007-04-13 21:54:58 +0000 @@ -343,6 +343,8 @@ function system_install() { has_children int NOT NULL default 0, tab int NOT NULL default 0, title varchar(255) NOT NULL default '', + title_callback varchar(255) NOT NULL default '', + title_arguments varchar(255) NOT NULL default '', parent varchar(255) NOT NULL default '', type int NOT NULL default 0, block_callback varchar(255) NOT NULL default '', @@ -812,6 +814,8 @@ function system_install() { has_children int NOT NULL default 0, tab int NOT NULL default 0, title varchar(255) NOT NULL default '', + title_callback varchar(255) NOT NULL default '', + title_arguments varchar(255) NOT NULL default '', parent varchar(255) NOT NULL default '', type int NOT NULL default 0, block_callback varchar(255) NOT NULL default '',