? missing_semi1981.patch ? multiple_menus_0.patch ? separate_menu_tables_1.patch ? separate_menu_tables_2.patch ? separate_menu_tables_3.patch Index: index.php =================================================================== RCS file: /cvs/drupal/drupal/index.php,v retrieving revision 1.93 diff -u -p -r1.93 index.php --- index.php 6 Apr 2007 13:27:20 -0000 1.93 +++ index.php 26 Apr 2007 04:03:51 -0000 @@ -11,7 +11,7 @@ require_once './includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); - +menu_rebuild(); $return = menu_execute_active_handler(); // Menu status constants are integers; page content is a string. ? includes/foo_menu.inc ? sites/rosalind.local Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.163 diff -u -p -r1.163 menu.inc --- includes/menu.inc 24 Apr 2007 08:26:58 -0000 1.163 +++ includes/menu.inc 26 Apr 2007 04:03:52 -0000 @@ -313,7 +313,7 @@ function menu_get_item($path = NULL, $it // behaviour. The last parent is always the item itself. $args = explode(',', $item->parents); $placeholders = implode(', ', array_fill(0, count($args), '%d')); - $result = db_query('SELECT * FROM {menu} WHERE mid IN ('. $placeholders .') ORDER BY mleft', $args); + $result = db_query('SELECT * FROM {menu_links} WHERE mid IN ('. $placeholders .') ORDER BY mleft', $args); $item->access = TRUE; while ($item->access && ($parent = db_fetch_object($result))) { $map = _menu_translate($parent, $original_map); @@ -582,78 +582,84 @@ 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. + * Build a list of named menus. */ -function menu_rebuild() { - // TODO: split menu and menu links storage. - $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; +function menu_get_names($reset = FALSE) { + $names = module_invoke_all('menu_info'); + $names['navigation'] = array('customize' => TRUE, 'title' => t('Navigation'), 'title callback' => 'check_plain', 'callback arguments' => array('$user->name')); + + foreach ($names as $key => $data) { + if (!isset($data['customize'])) { + $names[$key]['customize'] = TRUE; + } + if (!isset($data['title callback'])) { + $names[$key]['title callback'] = FALSE; + } + if (!isset($data['callback arguments'])) { + $names[$key]['callback arguments'] = array(); + } + } + return $names; +} + + +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); +} +function _menu_router_build($callbacks) { // First pass: separate callbacks from pathes, making pathes ready for // matching. Calculate fitness, and fill some default values. - foreach ($menu as $path => $item) { + $menu = array(); + foreach ($callbacks as $path => $item) { $load_functions = array(); $to_arg_functions = array(); $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; + + $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; + } + else { + if (function_exists($matches[1] .'_to_arg')) { + $to_arg_functions[$k] = $matches[1] .'_to_arg'; $load_functions[$k] = NULL; + $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 (function_exists($matches[1] .'_load')) { + $load_functions[$k] = $matches[1] .'_load'; + $match = TRUE; } } - if ($match) { - $parts[$k] = '%'; - } - else { - $fit |= 1 << ($slashes - $k); - } } - if ($fit) { - $move = TRUE; + if ($match) { + $parts[$k] = '%'; } else { - // If there is no %, it fits maximally. - $fit = (1 << $number_parts) - 1; + $fit |= 1 << ($slashes - $k); } } + if ($fit) { + $move = TRUE; + } + else { + // 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); $item += array( @@ -663,8 +669,6 @@ function menu_rebuild() { '_number_parts' => $number_parts, '_parts' => $parts, '_fit' => $fit, - '_mid' => $mid++, - '_children' => array(), ); $item += array( '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_TREE), @@ -672,65 +676,277 @@ function menu_rebuild() { ); if ($move) { $new_path = implode('/', $item['_parts']); - unset($menu[$path]); + $menu[$new_path] = $item; } else { - $new_path = $path; + $menu[$path] = $item; } - $menu_path_map[$path] = $new_path; - $menu[$new_path] = $item; } - // Alter the menu after the first preprocessing phase, keys are like user/%. - drupal_alter('menu', $menu, MENU_ALTER_PREPROCESSED); - $menu_path_map[''] = ''; - // Second pass: prepare for sorting and find parents. - foreach ($menu as $path => $item) { + // Apply inheritance rules. + foreach ($menu as $path => $v) { $item = &$menu[$path]; - $parent_path = $path; - $parents = array($item['_mid']); - $depth = 1; - if (isset($item['parent']) && isset($menu_path_map[$item['parent']])) { - $item['parent'] = $menu_path_map[$item['parent']]; + if (!isset($item['access callback']) && isset($item['access arguments'])) { + $item['access callback'] = 'user_access'; // Default callback } - 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']; + if (!$item['_tab']) { + // Non-tab items + $item['parent'] = ''; + } + 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 (0 && $parent_path == 'admin/logs') { + drupal_set_message($path ." ".print_r($parent,1)); } - else { - $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); + if (empty($item['parent'])) { + // parent stores the tab parent. + $item['parent'] = $parent_path; + } + // If a callback is not found, we try to find the first parent that + // has a callback. When found, its callback argument will also be + // copied. + if (!isset($item['access callback']) && isset($parent['access callback'])) { + $item['access callback'] = $parent['access callback']; + $item['access arguments'] = isset($parent['access arguments']) ? $parent['access arguments'] : array(); + } + // Same for page callbacks, except preserve arguments. + 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($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; + } + } + if (!isset($item['access callback']) || empty($item['page callback'])) { + $item['access callback'] = 0; + } + if (is_bool($item['access callback'])) { + $item['access callback'] = intval($item['access callback']); + } + + $insert_item = $item; + $item = NULL; + $item = $insert_item + array( + 'access arguments' => array(), + 'access callback' => '', + 'page arguments' => array(), + 'page callback' => '', + 'block callback' => '', + 'description' => '', + 'position' => '', + ); + db_query("INSERT INTO {menu} ( + path, load_functions, to_arg_functions, + access_callback, access_arguments, page_callback, page_arguments, fit, + number_parts, parent,tab_depth, title, + type, block_callback, description, position) + VALUES ('%s', '%s', '%s', + '%s', '%s', '%s', '%s', %d, + %d, '%s', %d, '%s', + %d, '%s', '%s', '%s')", + $path, $item['load_functions'], + $item['to_arg_functions'], $item['access callback'], + serialize($item['access arguments']), $item['page callback'], + serialize($item['page arguments']), $item['_fit'], + $item['_number_parts'], $item['parent'], $item['_tab'] ? $item['_number_parts'] : 0, + $item['title'],$item['type'], $item['block callback'], $item['description'], $item['position']); + } + return $menu; +} + + +/** + * For the items in a given named menu, find the access callback and other properties for each item. + */ +function _menu_links_build(&$ml, $menu) { + static $mid = 1; + + foreach ($ml as $path => $v) { + $item = &$ml[$path]; + + // Start fresh just to be sure + $item['access callback'] = NULL; + $item['access arguments'] = NULL; + + $item['_mid'] = $mid++; + if (!isset($item['_external'])) { + $item['_external'] = menu_path_is_external($path); + } + if ($item['_external']) { + $item['access callback'] = 1; + $item['access arguments'] = array(); + } + else { + $item['parts'] = explode('/', $path, 6); + $item['number_parts'] = count($item['parts']); + if (isset($menu[$path])) { // Perfect match, no need to search + $item['access callback'] = $menu[$path]['access callback']; + $item['access arguments'] = $menu[$path]['access arguments']; + } + else { + list($ancestors, $placeholders) = menu_get_ancestors($item['parts']); + array_shift($ancestors); + + while($path = array_shift($ancestors) && !isset($item['access callback'])) { + if (isset($menu[$path])) { + $item['access callback'] = $menu[$path]['access callback']; + $item['access arguments'] = $menu[$path]['access arguments']; } } + if (!isset($item['access callback'])) { // Invalid item + $item['access callback'] = 0; + $item['access arguments'] = array(); + } + } + + } + if (!isset($item['disabled'])) { + $item['disabled'] = FALSE; + } + } +} + + +/** + * For the items in a given named menu, find the parents and prepare to sort. + */ +function _menu_links_find_parents(&$ml) { + + foreach ($ml as $path => $v) { + $item = &$ml[$path]; + // Second pass: prepare for sorting and find parents. + + $parents = array($item['_mid']); + $depth = 1; + $p = $path; + + while ($p) { + $parent_path = isset($ml[$p]['parent']) ? $ml[$p]['parent'] : ''; + if (isset($ml[$parent_path])) { + $parent = $ml[$parent_path]; + $parents[] = $parent['_mid']; + $depth++; + if (!isset($item['_pid'])) { + $item['_pid'] = $parent['_mid']; + $item['_visible_parent_path'] = $parent_path; + } } + $p = $parent_path; } + $parents[] = 0; $parents = implode(',', array_reverse($parents)); // Store variables and set defaults. $item += array( '_pid' => 0, - '_depth' => ($item['_visible'] ? $depth : $item['_number_parts']), + '_depth' => $depth, '_parents' => $parents, - '_slashes' => $slashes, + //'_slashes' => $slashes, + '_children' => array(), ); // This sorting works correctly only with positive numbers, // so we shift negative weights to be positive. $sort[$path] = $item['_depth'] . sprintf('%05d', $item['weight'] + 50000) . $item['title']; - unset($item); + + // defaults for now TODO - real calculations + $item += array( + '_mleft' => 0, + '_mright' => 0, + 'block callback' => '', + 'description' => '', + 'attributes' => '', + 'query' => '', + 'fragment' => '', + 'absolute' => '', + 'html' => '', + 'has_children' => 0, + 'link_path' => '', + ); + } + array_multisort($sort, $ml); +} + +function _menu_links_save($menu_name, $ml) { + + foreach ($ml as $path => $item) { + db_query("INSERT INTO {menu_links} ( + menu_name, mid, pid, path, disabled, + access_callback, access_arguments, parents, depth, has_children, + mleft, mright, description, + link_path, attributes, query, fragment, absolute, html) + VALUES ('%s', %d, %d, '%s', %d, + '%s', '%s', '%s', %d, %d, + %d, %d, '%s', + '%s', '%s', '%s', '%s', %d, %d)", + $menu_name, $item['_mid'], $item['_pid'], $path, $item['disabled'], + $item['access callback'], serialize($item['access arguments']), $item['_parents'], + $item['_depth'], $item['has_children'], + $item['_mleft'], $item['_mright'], $item['description'], + $item['link_path'], + $item['attributes'], $item['query'], $item['fragment'], + $item['absolute'], $item['html']); } - array_multisort($sort, $menu); +} + +/** + * Populate the database representation of the menu. + */ +function menu_rebuild() { + + $callbacks = module_invoke_all('menu'); + $names = menu_get_names(TRUE); + + foreach ($callbacks as $path => $item) { + if (!isset($item['menu name']) || !isset($names[$item['menu name']])) { + $callbacks[$path]['menu name'] = 'navigation'; // The default menu + } + } + + // Alter the menu as defined in modules, keys are like user/%user. + + db_query('DELETE FROM {menu}'); + db_query('DELETE FROM {menu_links}'); + + drupal_alter('menu', $callbacks); + $menu = _menu_router_build($callbacks); + + $mid = 1; + + $ml = array(); // replace later ml = menu_links + foreach($names as $key => $n) { + $ml[$key] = array(); + } + + // Add normal and suggested items + foreach($menu as $path => $item) { + if ($item['type'] & MENU_MODIFIABLE_BY_ADMIN) { + $item['disabled'] = !$item['_visible']; + $ml[$item['menu name']][$path] = $item; + } + } + //TODO build admin page here as separate sets of menu links? + + // Alter the menu links derived from module-defined callbacks. + // This.is where menu.module will do its work adding and rearranging links + drupal_alter('menu_links', $ml); + + foreach($names as $key => $n) { + _menu_links_build($ml[$key], $menu); + _menu_links_find_parents($ml[$key]); + // TODO mleft mright children + _menu_links_save($key, $ml[$key]); + } + + + return; + + + // We are now sorted, so let's build the tree. $children = array(); @@ -742,77 +958,13 @@ function menu_rebuild() { menu_renumber($menu); // Apply inheritance rules. - foreach ($menu as $path => $item) { - 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. We do - // not inherit from visible parents which are themselves inherited. - if (!isset($item['access callback']) && isset($parent['access callback']) && !(isset($parent['access inherited']) && $parent['_visible'])) { - 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; - } - } + foreach ($names as $path => $item) { - // 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 ($item['_tab']) { - if (isset($item['parent'])) { - $item['_depth'] = $item['parent'] ? $menu[$item['parent']]['_depth'] + 1 : 1; - } - else { - $item['parent'] = implode('/', array_slice($item['_parts'], 0, $item['_number_parts'] - 1)); - } - } - else { - // Non-tab items specified the parent for visible links, and it's - // stored in parents, parent stores the tab parent. - $item['parent'] = $path; - } $insert_item = $item; unset($item); $item = $insert_item + array( - 'access arguments' => array(), - 'access callback' => '', - 'page arguments' => array(), - 'page callback' => '', '_mleft' => 0, '_mright' => 0, 'block callback' => '', @@ -838,35 +990,10 @@ function menu_rebuild() { break; } } - // We remove disabled items here -- this way they will be numbered in the - // tree so the menu overview screen can show them. - if (!empty($item['disabled'])) { - $item['_visible'] = FALSE; - } - db_query("INSERT INTO {menu} ( - mid, pid, path, load_functions, to_arg_functions, - access_callback, access_arguments, page_callback, page_arguments, fit, - number_parts, visible, parents, depth, has_children, tab, title, parent, - type, mleft, mright, block_callback, description, position, - link_path, attributes, query, fragment, absolute, html) - VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, - '%s', %d, %d, %d, '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', - '%s', '%s', '%s', '%s', %d, %d)", - $item['_mid'], $item['_pid'], $path, $item['load_functions'], - $item['to_arg_functions'], $item['access callback'], - serialize($item['access arguments']), $item['page callback'], - serialize($item['page arguments']), $item['_fit'], - $item['_number_parts'], $item['_visible'], $item['_parents'], - $item['_depth'], $has_children, $item['_tab'], - $item['title'], $item['parent'], $item['type'], $item['_mleft'], - $item['_mright'], $item['block callback'], $item['description'], - $item['position'], $link_path, - $item['attributes'], $item['query'], $item['fragment'], - $item['absolute'], $item['html']); - } -} + } +} function menu_renumber(&$tree) { foreach ($tree as $key => $element) { @@ -924,20 +1051,20 @@ function menu_local_tasks($level = 0) { continue; } // This loads all the tabs. - $result = db_query("SELECT * FROM {menu} WHERE parent = '%s' AND tab = 1 ORDER BY mleft", $parent); + $result = db_query("SELECT * FROM {menu} WHERE parent = '%s' AND tab_depth != 0", $parent); // ORDER BY mleft $tabs_current = ''; while ($item = db_fetch_object($result)) { // This call changes the path from for example user/% to user/123 and // also determines whether we are allowed to access it. _menu_translate($item, $map, MENU_RENDER_LINK); if ($item->access) { - $depth = $item->depth; + $depth = $item->tab_depth; $link = l($item->title, $item->link_path, (array)$item); // We check for the active tab. - if ($item->path == $router_item->path || (!$router_item->tab && $item->type == MENU_DEFAULT_LOCAL_TASK)) { + if ($item->path == $router_item->path || (!$router_item->tab_depth && $item->type == MENU_DEFAULT_LOCAL_TASK)) { $tabs_current .= theme('menu_local_task', $link, TRUE); // Let's try to find the router item one level up. - $next_router_item = db_fetch_object(db_query("SELECT path, tab, parent FROM {menu} WHERE path = '%s'", $item->parent)); + $next_router_item = db_fetch_object(db_query("SELECT path, tab_depth, parent FROM {menu} WHERE path = '%s'", $item->parent)); // We will need to inspect one level down. $parents[] = $item->path; } @@ -1009,7 +1136,7 @@ function menu_get_active_title() { * rendering. */ function menu_get_item_by_mid($mid) { - if ($item = db_fetch_object(db_query('SELECT * FROM {menu} WHERE mid = %d', $mid))) { + if ($item = db_fetch_object(db_query('SELECT * FROM {menu_links} WHERE mid = %d', $mid))) { _menu_translate($item, arg(), MENU_RENDER_LINK); if ($item->access) { return $item; Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.99 diff -u -p -r1.99 system.install --- modules/system/system.install 25 Apr 2007 21:34:32 -0000 1.99 +++ modules/system/system.install 26 Apr 2007 04:03:52 -0000 @@ -327,8 +327,6 @@ function system_install() { ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {menu} ( - mid int NOT NULL default 0, - pid int NOT NULL default 0, path varchar(255) NOT NULL default '', load_functions varchar(255) NOT NULL default '', to_arg_functions varchar(255) NOT NULL default '', @@ -338,32 +336,42 @@ function system_install() { page_arguments text, fit int NOT NULL default 0, number_parts int NOT NULL default 0, - mleft int NOT NULL default 0, - mright int NOT NULL default 0, - visible int NOT NULL default 0, - parents varchar(255) NOT NULL default '', - depth int NOT NULL default 0, - has_children int NOT NULL default 0, - tab int NOT NULL default 0, + parent varchar(255) NOT NULL default '', + tab_depth 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 '', description varchar(255) NOT NULL default '', position varchar(255) NOT NULL default '', + PRIMARY KEY (path), + KEY fit (fit) + ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); + + db_query("CREATE TABLE {menu_links} ( + menu_name varchar(64) NOT NULL default '', + mid int NOT NULL default 0, + pid int NOT NULL default 0, + path varchar(255) NOT NULL default '', + disabled tinyint NOT NULL default 0, + access_callback varchar(255) NOT NULL default '', + access_arguments text, + mleft int NOT NULL default 0, + mright int NOT NULL default 0, + parents varchar(255) NOT NULL default '', + depth int NOT NULL default 0, + has_children int NOT NULL default 0, + description varchar(255) NOT NULL default '', link_path varchar(255) NOT NULL default '', attributes varchar(255) NOT NULL default '', query varchar(255) NOT NULL default '', fragment varchar(255) NOT NULL default '', absolute INT NOT NULL default 0, html INT NOT NULL default 0, - PRIMARY KEY (path), - KEY fit (fit), + PRIMARY KEY (menu_name, path), KEY visible (visible), - KEY pid (pid), - KEY parent (parent) + KEY pid (pid) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {node} (