0; $i--) { $current = ''; $count = 0; for ($j = $length; $j >= 0; $j--) { if ($i & (1 << $j)) { $count++; $current .= $parts[$length - $j]; } else { $current .= '%'; } if ($j) { $current .= '/'; } } // If the number was like 10...0 then the next number will be 11...11, // one bit less wide. if ($count == 1) { $length--; } $placeholders[] = "'%s'"; $ancestors[] = $current; } return array($ancestors, $placeholders); } /** * The menu system uses serialized arrays stored in the database for * arguments. However, often these need to change according to the * current path. This function unserializes such an array and does the * necessary change. * * Integer values are mapped according to the $map parameter. For * example, if unserialize($data) is array('node_load', 1) and $map is * array('node', '12345') then 'node_load' will not be changed * because it is not an integer, but 1 will as it is an integer. As * $map[1] is '12345', 1 will be replaced with '12345'. So the result * will be array('node_load', '12345'). * * @param @data * A serialized array. * @param @map * An array of potential replacements. * @return * The $data array unserialized and mapped. */ function menu_unserialize($data, $map) { if ($data = unserialize($data)) { foreach ($data as $k => $v) { if (is_int($v)) { $data[$k] = isset($map[$v]) ? $map[$v] : ''; } } return $data; } else { return array(); } } /** * Replaces the statically cached item for a given path. * * @param $path * The path * @param $item * The menu item. This is a menu entry, an associative array, * with keys like title, access callback, access arguments etc. */ function menu_set_item($path, $item) { menu_get_item($path, TRUE, $item); } function menu_get_item($path = NULL, $execute = TRUE, $item = NULL) { static $items; if (!isset($path)) { $path = $_GET['q']; } if (isset($item)) { $items[$path] = $item; } if (!isset($items[$path])) { $map = arg(NULL, $path); $parts = array_slice($map, 0, 6); list($ancestors, $placeholders) = menu_get_ancestors($parts); if ($item = db_fetch_object(db_query_range('SELECT * FROM {menu} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) { $item->access = _menu_access($item, $map); if ($map === FALSE) { $items[$path] = FALSE; return FALSE; } if ($execute) { $item->page_arguments = array_merge(menu_unserialize($item->page_arguments, $map), array_slice($parts, $item->number_parts)); } } $items[$path] = $item; } return $items[$path]; } /** * Execute the handler associated with the active menu item. */ function menu_execute_active_handler() { if ($item = menu_get_item()) { return $item->access ? call_user_func_array($item->page_callback, $item->page_arguments) : MENU_ACCESS_DENIED; } return MENU_NOT_FOUND; } function _menu_access($item, &$map) { if ($item->map_callback) { $map = call_user_func_array($item->map_callback, array_merge(array($map), unserialize($item->map_arguments))); if ($map === FALSE) { return FALSE; } } $callback = $item->access_callback; if (is_numeric($callback)) { return $callback; } $arguments = menu_unserialize($item->access_arguments, $map); // As call_user_func_array is quite slow and user_access is a very common // callback, it is worth making a special case for it. if ($callback == 'user_access') { return (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]); } return call_user_func_array($callback, $arguments); } /** * Returns a rendered menu tree. */ function menu_tree() { $item = menu_get_item(); list(, $menu) = _menu_tree(db_query('SELECT * FROM {menu} WHERE pid IN ('. $item->parents .') AND visible = 1 ORDER BY vancode')); return $menu; } function _menu_tree($result = NULL, $depth = 0, $link = array('link' => '', 'has_children' => FALSE)) { static $original_map; $remnant = array('link' => '', 'has_children' => FALSE); $tree = ''; while ($item = db_fetch_object($result)) { $map = arg(NULL, $item->path); if (!_menu_access($item, $map)) { continue; } $menu_link = array('link' => $item->menu_link, 'has_children' => $item->has_children); if ($item->depth > $depth) { list($remnant, $menu) = _menu_tree($result, $item->depth, $menu_link); $tree .= theme('menu_tree', $link, $menu); $link = $remnant; $remnant = ''; } elseif ($item->depth == $depth) { $tree .= theme('menu_link', $link); $link = $menu_link; } else { $remnant = $menu_link; break; } } if ($link['link']) { $tree .= theme('menu_link', $link); } return array($remnant, $tree); } /** * Generate the HTML for a menu tree. */ function theme_menu_tree($link, $tree) { $tree = ''; return $link['link'] ? theme('menu_link', $link, $tree) : $tree; } /** * Generate the HTML for a menu link. */ function theme_menu_link($link, $menu = '') { return '
  • '. $link['link'] . $menu .'
  • ' . "\n"; } /** * Returns the help associated with the active menu item. */ function menu_get_active_help() { $path = $_GET['q']; $output = ''; $item = menu_get_item(); if (!$item->access) { // Don't return help text for areas the user cannot access. return; } foreach (module_list() as $name) { if (module_hook($name, 'help')) { if ($temp = module_invoke($name, 'help', $path)) { $output .= $temp ."\n"; } if (module_hook('help', 'page')) { if (arg(0) == "admin") { if (module_invoke($name, 'help', 'admin/help#'. arg(2)) && !empty($output)) { $output .= theme("more_help_link", url('admin/help/'. arg(2))); } } } } } return $output; } /** * Populate the database representation of the menu. */ function menu_rebuild() { $next = array(); db_query('DELETE FROM {menu}'); $menu = module_invoke_all('menu'); foreach (module_implements('menu_alter') as $module) { $function = $module .'_menu_alter'; $function($menu); } $mid = 1; // First pass. foreach ($menu as $path => $item) { $item = &$menu[$path]; $parts = explode('/', $path, 6); $number_parts = count($parts); // We store the highest index of parts here to save some work in the weight // calculation loop. $slashes = $number_parts - 1; // If there is no %, it fits maximally. if (strpos($path, '%') === FALSE) { $fit = (1 << $number_parts) - 1; } else { // We need to calculate the fitness. $fit = 0; foreach ($parts as $k => $part) { // ($part != '%') is the bit we want and we shift it to its place // by shifting to left by ($slashes - $k) bits. $fit |= ($part != '%') << ($slashes - $k); } } if (!isset($item['_visible'])) { $item['_visible'] = (!isset($item['type']) || ($item['type'] & MENU_VISIBLE_IN_TREE)) ? 1 : 0; } $depth = 1; if (!isset($item['_mid'])) { $item['_mid'] = $mid++; } $parents = array($item['_mid']); for ($i = $slashes; $i; $i--) { $parent_path = implode('/', array_slice($parts, 0, $i)); // We need to calculate depth to be able to sort. depth needs visibility. if (isset($menu[$parent_path])) { $parent = &$menu[$parent_path]; // It's possible that the parent was not processed yet. if (!isset($parent['_mid'])) { $parent['_mid'] = $mid++; } if (!isset($parent['_visible'])) { $parent['_visible'] = (!isset($parent['type']) || ($parent['type'] & MENU_VISIBLE_IN_TREE)) ? 1 : 0; } if ($item['_visible'] && $parent['_visible']) { $parent['_has_children'] = 1; $depth++; $parents[] = $parent['_mid']; if (!isset($item['_pid'])) { $item['_pid'] = $parent['_mid']; $item['_visible_parent_path'] = $parent_path; } } unset($parent); } } $parents[] = 0; $parents = implode(',', array_reverse($parents)); // Store variables and set defaults. $item += array( '_fit' => $fit, '_number_parts' => $number_parts, '_parts' => $parts, '_pid' => 0, '_depth' => $depth, '_parents' => $parents, '_has_children' => 0, 'title' => '', 'weight' => 0, ); $sort[$path] = ($item['_visible'] ? $depth : $number_parts) . sprintf('%05d', $item['weight']) . $item['title']; unset($item); } array_multisort($sort, $menu); // Second pass: calculate ancestors, vancode and store into the database. 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. foreach (array('access', 'map', 'page') as $type) { if (!isset($item["$type callback"]) && isset($parent["$type callback"])) { $item["$type callback"] = $parent["$type callback"]; if (!isset($item["$type arguments"]) && isset($parent["$type arguments"])) { $item["$type arguments"] = $parent["$type arguments"]; } } } } } if (!isset($item['access callback'])) { $menu[$path]['access callback'] = isset($item['access arguments']) ? 'user_access' : 0; } if (!isset($item['map callback']) && isset($item['map arguments'])) { $item['map callback'] = 'menu_map'; } foreach (array('access', 'map', 'page') as $type) { if (isset($item["$type callback"]) && !isset($item["$type arguments"])) { $item["$type arguments"] = array(); } } if (is_bool($item['access callback'])) { $item['access callback'] = intval($item['access callback']); } if ($item['_visible']) { $prefix = isset($item['_visible_parent_path']) ? $menu[$item['_visible_parent_path']]['_prefix'] : ''; if (!isset($next[$prefix])) { $next[$prefix] = 0; } $vancode = $prefix . int2vancode($next[$prefix]++); $menu[$path]['_prefix'] = $vancode .'.'; $link = l($item['title'], $path, isset($item['attributes']) ? $item['attributes'] : array(), isset($item['query']) ? $item['query'] : NULL, isset($item['fragment']) ? $item['fragment'] : NULL); } else { $vancode = ''; $link = ''; } db_query("INSERT INTO {menu} (mid, pid, path, access_callback, access_arguments, page_callback, page_arguments, map_callback, map_arguments, fit, number_parts, vancode, menu_link, visible, parents, depth, has_children) VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, '%s', %d, %d)", $item['_mid'], $item['_pid'], $path, $item['access callback'], serialize($item['access arguments']), $item['page callback'], serialize($item['page arguments']), $item['map callback'], serialize($item['map arguments']), $item['_fit'], $item['_number_parts'], $vancode .'+', $link, $item['_visible'], $item['_parents'], $item['_depth'], $item['_has_children']); // $item needs to be unset because of the reference above. unset($item); } } function menu_map($arg, $function, $index, $default = FALSE) { $arg[$index] = is_numeric($arg[$index]) ? $function($arg[$index]) : $default; return $arg[$index] ? $arg : FALSE; } // Placeholders. function menu_primary_links() { } function menu_secondary_links() { } function menu_primary_local_tasks() { } function menu_secondary_local_tasks() { } function menu_set_active_item() { } function menu_set_location() { } function menu_get_active_breadcrumb() { return array(l(t('Home'), '')); }