Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.166
diff -u -p -r1.166 menu.inc
--- includes/menu.inc 16 May 2007 13:45:16 -0000 1.166
+++ includes/menu.inc 19 May 2007 19:36:20 -0000
@@ -149,11 +149,6 @@ define('MENU_SITE_OFFLINE', 4);
* @} End of "Menu status codes".
*/
-
-/**
- * @} End of "Menu operations."
- */
-
/**
* @Name Menu tree parameters
* @{
@@ -511,13 +506,13 @@ function _menu_link_translate(&$item) {
$item->access = FALSE;
return FALSE;
}
- if (!_menu_load_objects($item, $map)) {
- // An error occured loading an object
- $item->access = FALSE;
- return FALSE;
- }
// TODO: menu_tree may set this ahead of time for links to nodes
if (!isset($item->access)) {
+ if (!_menu_load_objects($item, $map)) {
+ // An error occured loading an object
+ $item->access = FALSE;
+ return FALSE;
+ }
_menu_check_access($item, $map);
}
// If the link title matches that of a router item, localize it.
@@ -578,6 +573,49 @@ function menu_tree_output($tree) {
}
/**
+ * Get the full data structure representing a named menu tree. Since this can be
+ * the full tree with no access checks done and including hidden items, the
+ * data returned may be used for generating an an admin interface or a select.
+ *
+ * @param $menu_name
+ * The named menu links to return
+ * @param $item
+ * A fully loaded menu link object, or NULL. If an object is supplied, the
+ * path to root will be set in the returned tree.
+ * @param $show_hidden
+ * @param $check_access
+ * @return
+ * An array of menu links, in the order they should be rendered.
+ */
+function menu_tree_all_data($menu_name = 'navigation', $item = NULL, $show_hidden = FALSE, $check_access = TRUE) {
+ static $tree = array();
+
+ $mlid = isset($item->mild) ? $item->mild : 0;
+ $key = $menu_name .':'. $mlid .':'. (int)$check_access .':'. (int)$show_hidden;
+
+ if (!isset($tree[$key])) {
+ if ($mlid) {
+ $parents = array($item->p1, $item->p2, $item->p3, $item->p4, $item->p5);
+ }
+ else {
+ $parents = array();
+ }
+ if (!$show_hidden) {
+ $where = ' AND ml.hidden = 0';
+ }
+ else {
+ $where = ' AND ml.hidden != '.MENU_CALLBACK;
+ }
+ list(, $tree[$key]) = _menu_tree_data(db_query("
+ SELECT *, ml.weight + 50000 AS weight FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
+ WHERE ml.menu_name = '%s'". $where ."
+ ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC", $menu_name), $parents);
+ }
+
+ return $tree[$key];
+}
+
+/**
* 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 invidual
* field, see http://drupal.org/node/141866 for more.
@@ -628,7 +666,7 @@ function menu_tree_data($menu_name = 'na
// No need to order by p6 - there is a sort by weight later.
list(, $tree[$menu_name]) = _menu_tree_data(db_query("
SELECT *, ml.weight + 50000 AS weight 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 .")
+ 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);
// TODO: cache_set() for the untranslated links
@@ -898,6 +936,25 @@ function menu_secondary_local_tasks() {
return menu_local_tasks(1);
}
+/**
+ * Returns the rendered local tasks. The default implementation renders
+ * them as tabs.
+ *
+ * @ingroup themeable
+ */
+function theme_menu_local_tasks() {
+ $output = '';
+
+ if ($primary = menu_primary_local_tasks()) {
+ $output .= "
\n";
+ }
+ if ($secondary = menu_secondary_local_tasks()) {
+ $output .= "\n";
+ }
+
+ return $output;
+}
+
function menu_set_active_menu_name($menu_name = NULL) {
static $active;
@@ -1004,36 +1061,14 @@ function menu_get_active_title() {
function menu_get_item_by_mlid($mlid) {
if ($item = db_fetch_object(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE mlid = %d", $mlid))) {
_menu_link_translate($item);
- if ($item->access) {
- return $item;
- }
+ return $item;
}
return FALSE;
}
-/**
- * Returns the rendered local tasks. The default implementation renders
- * them as tabs.
- *
- * @ingroup themeable
- */
-function theme_menu_local_tasks() {
- $output = '';
-
- if ($primary = menu_primary_local_tasks()) {
- $output .= "\n";
- }
- if ($secondary = menu_secondary_local_tasks()) {
- $output .= "\n";
- }
-
- return $output;
-}
-
function menu_cache_clear($menu_name = 'navigation') {
- // TODO: starting stub. This will be called whenever an item is added to or
- // moved from a named menu
+ cache_clear_all($menu_name .':', 'cache_menu', TRUE);
}
/**
@@ -1041,8 +1076,7 @@ function menu_cache_clear($menu_name = '
* router items or menu links.
*/
function menu_cache_clear_all() {
- cache_clear_all('*', 'menu_links', TRUE);
- cache_clear_all('*', 'menu_router', TRUE);
+ cache_clear_all('*', 'cache_menu', TRUE);
}
/**
@@ -1074,15 +1108,17 @@ function _menu_navigation_links_rebuild(
if ($item['type'] == MENU_CALLBACK || $item['type'] == MENU_SUGGESTED_ITEM) {
$item['hidden'] = $item['type'];
}
+ // Note, we set this as 'system', so that we can be sure to distinguish all
+ // the menu links generated automatically from entries in {menu_router}.
+ $item['module'] = 'system';
$item += array(
- 'menu name' => 'navigation',
+ 'menu_name' => 'navigation',
'link_title' => $item['title'],
'href' => $path,
- 'module' => 'system',
'hidden' => 0,
);
// We add nonexisting items.
- if ($item['_visible'] && !db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $item['menu name'], $item['href']))) {
+ if ($item['_visible'] && !db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $item['menu_name'], $item['href']))) {
$menu_links[$path] = $item;
$sort[$path] = $item['_number_parts'];
}
@@ -1101,12 +1137,34 @@ function _menu_navigation_links_rebuild(
}
/**
+ * Delete a menu link.
+ *
+ * @param $mlid
+ * A valid menu link mlid
+ */
+function menu_link_delete($mlid) {
+
+ $item = menu_get_item_by_mlid($mlid);
+
+ // System-created items get automatically deleted, but only on menu rebuild.
+ if ($item && $item->module != 'system') {
+
+ if ($item->has_children) {
+ // TODO - reparent children?
+ }
+ db_query('DELETE FROM {menu_links} WHERE mild = %d', $mlid);
+ menu_cache_clear($item->menu_name);
+ }
+}
+
+
+/**
* Save a menu link.
*
* @param $item
* An array representing a menu link item. The only mandatory keys are href
* and link_title. Possible keys are
- * menu name default is navigation
+ * menu_name default is navigation
* weight default is 0
* expanded whether the item is expanded.
* options An array of options, @see l for more.
@@ -1130,7 +1188,7 @@ function menu_link_save(&$item, $_menu =
$item['_external'] = menu_path_is_external($item['href']);
// Load defaults.
$item += array(
- 'menu name' => 'navigation',
+ 'menu_name' => 'navigation',
'weight' => 0,
'link_title' => '',
'hidden' => 0,
@@ -1138,26 +1196,24 @@ function menu_link_save(&$item, $_menu =
'expanded' => 0,
'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
);
- $existing_item = array();
+ $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 href = '%s'", $item['menu name'], $item['href']));
+ $existing_item = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE menu_name = '%s' AND href = '%s'", $menu_name, $item['href']));
}
- if (empty($existing_item)) {
+ if (!$existing_item) {
$item['mlid'] = db_next_id('{menu_links}_mlid');
}
- $menu_name = $item['menu name'];
- $new_path = !$existing_item || ($existing_item['href'] != $item['href']);
-
// Find the parent.
if (isset($item['plid'])) {
$parent = db_fetch_array(db_query("SELECT * FROM {menu_links} WHERE mlid = %d", $item['plid']));
}
- else { //
+ else {
$parent_path = $item['href'];
do {
$parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
@@ -1180,21 +1236,24 @@ function menu_link_save(&$item, $_menu =
}
else {
// Cannot add beyond the maximum depth.
- if ($parent['depth'] >= (MENU_MAX_DEPTH)) {
+ if ($item['has_children'] && $existing_item) {
+ $limit = MENU_MAX_DEPTH - menu_children_relative_depth($existing_item) - 1;
+ }
+ else {
+ $limit = MENU_MAX_DEPTH - 1;
+ }
+ if ($parent['depth'] > $limit) {
return FALSE;
}
$item['depth'] = $parent['depth'] + 1;
- _menu_parents_copy($item, $parent);
- $item['p'. $item['depth']] = $item['mlid'];
+ _menu_link_parents_set($item, $parent);
}
-
- if ($item['plid'] != $existing_item['plid']) {
-
- // TODO: UPDATE the parents of the children of the current item
- // TODO: check the has_children status of the previous parent
+ // Need to check both plid and menu_name, since plid can be 0 in any menu.
+ if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) {
+ _menu_link_move_children($item, $existing_item);
}
// Find the callback.
- if (empty($item['router_path']) || $new_path) {
+ if (empty($item['router_path']) || !$existing_item || ($existing_item['href'] != $item['href'])) {
if ($item['_external']) {
$item['router_path'] = '';
}
@@ -1213,13 +1272,13 @@ function menu_link_save(&$item, $_menu =
}
}
}
- if (!empty($existing_item)) {
+ if ($existing_item) {
db_query("UPDATE {menu_links} SET menu_name = '%s', plid = %d, href = '%s',
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",
- $item['menu name'], $item['plid'], $item['href'],
+ $item['menu_name'], $item['plid'], $item['href'],
$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'],
@@ -1237,18 +1296,19 @@ function menu_link_save(&$item, $_menu =
%d, %d, %d,
%d, %d, %d, %d, %d, %d,
'%s', '%s', '%s')",
- $item['menu name'], $item['mlid'], $item['plid'], $item['href'],
+ $item['menu_name'], $item['mlid'], $item['plid'], $item['href'],
$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']));
- }
-
- if ($item['plid'] && !$item['hidden']) {
- db_query("UPDATE {menu_links} SET has_children = 1 WHERE mlid = %d", $item['plid']);
+ }
+ // 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']));
+ db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $item['plid']);
}
- // Keep track of which menus have expanded items
+ // Keep track of which menus have expanded items.
$names = array();
$result = db_query("SELECT menu_name FROM {menu_links} WHERE expanded != 0 GROUP BY menu_name");
while ($n = db_fetch_array($result)) {
@@ -1258,13 +1318,94 @@ function menu_link_save(&$item, $_menu =
return TRUE;
}
-function _menu_parents_copy(&$dest, $source, $offset = 0){
- $i = 1 + $offset;
- $depth = 0;
- for ($j = 1; $i <= MENU_MAX_DEPTH; $i++) {
- $dest['p'. $i] = $source['p'. $j];
- $j++;
- }
+/**
+ * Find the depth of an item's children relative to its depth. For example, if
+ * the item has a depth of 2, and the maximum of any child in the menu link tree
+ * is 5, the relative depth is 3.
+ *
+ * @param $item
+ * An array representing a menu link item.
+ * @return
+ * The relative depth, or zero.
+ *
+ */
+function menu_link_children_relative_depth($item) {
+ $i = 1;
+ $match = '';
+ $args[] = $item['menu_name'];
+ $p = 'p1';
+ while ($i <= MENU_MAX_DEPTH && $item[$p]) {
+ $match .= " AND $p = %d";
+ $args[] = $item[$p];
+ $p = 'p'. ++$i;
+ }
+
+ $max_depth = db_result(db_query_range("SELECT depth FROM {menu_links} WHERE menu_name = '%s'". $match ." ORDER BY depth DESC", $args, 0, 1));
+
+ return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0;
+}
+
+/**
+ * Update the menu name, parents (p1 - p6), and depth for the children of
+ * a menu link that's being moved in the tree and check the has_children status
+ * of the previous parent.
+ */
+function _menu_link_move_children($item, $existing_item) {
+
+ $args[] = $item['menu_name'];
+ $set = '';
+ $shift = $item['depth'] - $existing_item['depth'];
+ if ($shift < 0) {
+ $args[] = -$shift;
+ $set = ', depth = depth - %d';
+ }
+ elseif ($shift > 0) {
+ $args[] = $shift;
+ $set = ', depth = depth + %d';
+ }
+ $i = 1;
+ while ($i <= $item['depth']) {
+ $p = 'p'. $i++;
+ $set .= ", $p = %d";
+ $args[] = $item[$p];
+ }
+ $j = $existing_item['depth'] + 1;
+ while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
+ $set .= ', p'. $i++ .' = p'. $j++;
+ }
+ while ($i <= MENU_MAX_DEPTH) {
+ $set .= ', p'. $i++ .' = 0';
+ }
+
+ $args[] = $existing_item['menu_name'];
+ $i = 1;
+ while ($i <= MENU_MAX_DEPTH && $existing_item[$p]) {
+ $p = 'p'. $i++;
+ $match .= " AND $p = %d";
+ $args[] = $existing_item[$p];
+ }
+
+ db_query("UPDATE {menu_links} SET menu_name = '%s'". $set ." WHERE menu_name = '%s'". $match, $args);
+
+ if ($existing_item['plid']) {
+ $parent_has_children = (bool)db_result(db_query("SELECT COUNT(*) FROM {menu_links} WHERE plid = %d AND hidden = 0 AND mlid != %d",$existing_item['plid'], $existing_item['mlid']));
+ db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $existing_item['plid']);
+ }
+}
+
+function _menu_link_parents_set(&$item, $parent){
+ $i = 1;
+ while ($i < $item['depth']) {
+ $p = 'p'. $i++;
+ $item[$p] = $parent[$p];
+ }
+ $p = 'p'. $i++;
+ // The parent (p1 - p6) corresponding to the depth always equals the mlid.
+ $item[$p] = $item['mlid'];
+ while ($i <= MENU_MAX_DEPTH) {
+ $p = 'p'. $i++;
+ $item[$p] = 0;
+ }
}
function _menu_router_build($callbacks) {
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.112
diff -u -p -r1.112 system.install
--- modules/system/system.install 18 May 2007 07:03:21 -0000 1.112
+++ modules/system/system.install 19 May 2007 19:36:21 -0000
@@ -265,6 +265,17 @@ function system_install() {
INDEX expire (expire)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+ db_query("CREATE TABLE {cache_menu} (
+ cid varchar(255) BINARY NOT NULL default '',
+ data longblob,
+ expire int NOT NULL default '0',
+ created int NOT NULL default '0',
+ headers text,
+ serialized int(1) NOT NULL default '0',
+ PRIMARY KEY (cid),
+ INDEX expire (expire)
+ ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+
db_query("CREATE TABLE {comments} (
cid int NOT NULL auto_increment,
pid int NOT NULL default '0',
@@ -398,7 +409,9 @@ function system_install() {
link_title varchar(255) NOT NULL default '',
options text,
PRIMARY KEY (mlid),
- KEY parents (plid, p1, p2, p3, p4, p5),
+ KEY router_path (router_path),
+ KEY plid (plid),
+ KEY parents (p1, p2, p3, p4, p5),
KEY menu_name_path (menu_name, href),
KEY menu_expanded_children (expanded, has_children)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
@@ -780,10 +793,19 @@ function system_install() {
serialized smallint NOT NULL default '0',
PRIMARY KEY (cid)
)");
- db_query("CREATE INDEX {cache}_expire_idx ON {cache} (expire)");
+ db_query("CREATE TABLE {cache_menu} (
+ cid varchar(255) NOT NULL default '',
+ data bytea,
+ expire int NOT NULL default '0',
+ created int NOT NULL default '0',
+ headers text,
+ serialized smallint NOT NULL default '0',
+ PRIMARY KEY (cid)
+ )"); db_query("CREATE INDEX {cache}_expire_idx ON {cache} (expire)");
db_query("CREATE INDEX {cache_filter}_expire_idx ON {cache_filter} (expire)");
db_query("CREATE INDEX {cache_page}_expire_idx ON {cache_page} (expire)");
db_query("CREATE INDEX {cache_form}_expire_idx ON {cache_form} (expire)");
+ db_query("CREATE INDEX {cache_menu}_expire_idx ON {cache_form} (expire)");
db_query("CREATE TABLE {comments} (
cid serial,
@@ -919,7 +941,10 @@ function system_install() {
options text,
PRIMARY KEY (mlid)
)");
- db_query("CREATE INDEX {menu_links}_parents_idx ON {menu_links} (plid, p1, p2, p3, p4, p5)");
+
+ db_query("CREATE INDEX {menu_links}_router_path_idx ON {menu_links} (router_path)");
+ db_query("CREATE INDEX {menu_links}_plid_idx ON {menu_links} (plid)");
+ db_query("CREATE INDEX {menu_links}_parents_idx ON {menu_links} (p1, p2, p3, p4, p5)");
db_query("CREATE INDEX {menu_links}_menu_name_idx ON {menu_links} (menu_name, href)");
db_query("CREATE INDEX {menu_links}_expanded_children_idx ON {menu_links} (expanded, has_children)");
@@ -3919,14 +3944,15 @@ function system_update_6012() {
db_add_column($ret, 'cache', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
db_add_column($ret, 'cache_filter', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
db_add_column($ret, 'cache_page', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
+ db_add_column($ret, 'cache_menu', 'serialized', 'smallint', array('default' => "'0'", 'not null' => TRUE));
break;
case 'mysql':
case 'mysqli':
$ret[] = update_sql("ALTER TABLE {cache} ADD serialized int(1) NOT NULL default '0'");
$ret[] = update_sql("ALTER TABLE {cache_filter} ADD serialized int(1) NOT NULL default '0'");
$ret[] = update_sql("ALTER TABLE {cache_page} ADD serialized int(1) NOT NULL default '0'");
+ $ret[] = update_sql("ALTER TABLE {cache_menu} ADD serialized int(1) NOT NULL default '0'");
break;
-
}
return $ret;