Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.154
diff -u -r1.154 menu.inc
--- includes/menu.inc	14 Feb 2007 16:43:38 -0000	1.154
+++ includes/menu.inc	26 Feb 2007 20:59:32 -0000
@@ -350,7 +350,7 @@
  *   with objects loaded where appropriate and the third is the path ready for
  *   printing.
  */
-function _menu_translate($item, $map, $operation = MENU_HANDLE_REQUEST) {
+function _menu_translate($item, $map, $operation = MENU_HANDLE_REQUEST, $access_check = TRUE) {
   $path = '';
 
   // Check if there are dynamic arguments in the path that need to be calculated.
@@ -376,7 +376,7 @@
         }
       }
       // We now have a real path regardless of operation, map it.
-      if ($load_function) {
+      if ($access_check && $load_function) {
         $return = $load_function(isset($path_map[$index]) ? $path_map[$index] : '');
         // If callback returned an error or there is no callback, trigger 404.
         if ($return === FALSE) {
@@ -393,6 +393,9 @@
   else {
     $path = $item->path;
   }
+  if (!$access_check) {
+    return array(TRUE, $map, $path);
+  }
 
   // Determine access callback, which will decide whether or not the current user has
   // access to this path.
@@ -415,8 +418,52 @@
  * Returns a rendered menu tree.
  */
 function menu_tree() {
+  global $user;
   if ($item = menu_get_item()) {
-    list(, $menu) = _menu_tree(db_query('SELECT * FROM {menu} WHERE pid IN ('. $item->parents .') AND visible = 1 ORDER BY vancode'));
+    if ($user->uid == 1) {
+      $result = db_query('SELECT *, 1 AS rid FROM {menu} WHERE pid IN ('. $item->parents .') AND visible = 1 ORDER BY mleft');
+    }
+    else {
+      $rids = array_keys($user->roles);
+      // This is signs that the item needs dynamic permissions.
+      $rids[] = 0;
+      $result = db_query('SELECT m.*, MAX(rid) AS rid FROM {menu} m INNER JOIN JOIN {menu_roles_cache} mr ON m.mid = mr.mid WHERE mr.page_mid = %d AND mr.rid IN (%s) GROUP BY m.mid ORDER BY mleft', $item->mid, $rids);
+      if (!db_num_rows($result)) {
+        $menu_empty = variable_get('menu_empty', array());
+        $menu_empty_new = $menu_empty;
+        // As only visible items have entires in menu_roles, there is no need
+        // for visible = 1 checking.
+        // TODO: split menu and menu links storage.
+        foreach ($rids as $rid) {
+          if (!empty($menu_empty[$item->mid][$rid])) {
+            $result = '
+              SELECT children.mid
+              FROM {menu} children
+              INNER JOIN {menu_roles} mr ON children.mid = mr.mid AND rid = %d
+              WHERE NOT EXISTS
+                (SELECT 1 FROM {menu} parent
+                INNER JOIN {menu_roles} mr ON parent.mid = mr.mid
+                WHERE parent.mleft < children.mleft AND children.mright < parent.mright
+                LIMIT 1)
+                OR pid IN ('. $item->parents .')
+              ORDER BY mleft';
+            if (!db_num_rows($result)) {
+              $menu_empty_new[$item->mid][$rid] = TRUE;
+            }
+            else {
+              while ($insert_item = db_fetch_object($result)) {
+                db_query('INSERT INTO {menu_roles_cache} (mid, page_mid, rid) VALUES (%d, %d, %d)', $insert_item->mid, $item->mid, $rid);
+              }
+            }
+          }
+        }
+        if ($menu_empty != $menu_empty_new) {
+          variable_set('menu_empty', $menu_empty_new);
+        }
+        $result = db_query('SELECT m.*, MAX(rid) AS rid FROM {menu} m INNER JOIN JOIN {menu_roles_cache} mr ON m.mid = mr.mid WHERE mr.page_mid = %d AND mr.rid IN (%s) ORDER BY mleft', $item->mid, $rids);
+      }
+    }
+    list(, $menu) = _menu_tree($result);
     return $menu;
   }
 }
@@ -427,9 +474,14 @@
   $tree = '';
   $map = arg(NULL);
   while ($item = db_fetch_object($result)) {
-    list($access, , $path) = _menu_translate($item, $map, MENU_RENDER_LINK);
-    if (!$access) {
-      continue;
+    if (!$item->rid || $item->to_arg_functions) {
+      list($access, , $path) = _menu_translate($item, $map, MENU_RENDER_LINK, !$item->rid);
+      if (!$item->rid && !$access) {
+        continue;
+      }
+    }
+    else {
+      $path = $item->path;
     }
     $menu_link = array('link' => l($item->title, $path), 'has_children' => $item->has_children);
     if ($item->depth > $depth) {
@@ -506,8 +558,17 @@
  * Populate the database representation of the menu.
  */
 function menu_rebuild() {
-  $next = array();
+  $permissions = array();
+  $result = db_query('SELECT * FROM {permission}');
+  while ($permission_role = db_fetch_object($result)) {
+    foreach(explode(', ', $permission_role->perm) as $permission) {
+      $permissions[$permission][] = $permission_role->rid;
+    }
+  }
   db_query('DELETE FROM {menu}');
+  db_query('DELETE FROM {menu_roles}');
+  db_query('DELETE FROM {menu_roles_cache}');
+  variable_set('menu_empty', array());
   $menu = module_invoke_all('menu');
   foreach (module_implements('menu_alter') as $module) {
     $function = $module .'_menu_alter';
@@ -532,7 +593,6 @@
         if (empty($matches[1])) {
           $match = TRUE;
           $load_functions[$k] = NULL;
-          $to_arg_functions[$k] = NULL;
         }
         else {
           if (function_exists($matches[1] .'_to_arg')) {
@@ -571,6 +631,7 @@
       '_parts' => $parts,
       '_fit' => $fit,
       '_mid' => $mid++,
+      '_children' => array(),
     );
     $item += array(
       '_visible' => (bool)($item['type'] & MENU_VISIBLE_IN_TREE),
@@ -583,37 +644,33 @@
     else {
       $new_path = $path;
     }
+    $menu_path_map[$path] = $new_path;
     $menu[$new_path] = $item;
   }
-  // Second pass: find visible parents and prepare for sorting.
+  $menu_path_map[''] = '';
+  // Second pass: prepare for sorting and find parents.
   foreach ($menu as $path => $item) {
     $item = &$menu[$path];
     $number_parts = $item['_number_parts'];
-    $parents = array($item['_mid']);
-    if ($item['_visible'] && isset($item['parent'])) {
-      $parent_parts = explode('/', $item['parent'], 6);
+    if (isset($item['parent'])) {
+      $parent_parts = explode('/', $menu_path_map[$item['parent']], 6);
       $slashes = count($parent_parts) - 1;
     }
     else {
       $parent_parts = $item['_parts'];
-      $slashes = $number_parts -1;
+      $slashes = $number_parts - 1;
     }
     $depth = 1;
+    $parents = array($item['_mid']);
     for ($i = $slashes; $i; $i--) {
       $parent_path = implode('/', array_slice($parent_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];
-        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;
-          }
+      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'];
         }
-        unset($parent);
       }
     }
     $parents[] = 0;
@@ -621,15 +678,37 @@
     // Store variables and set defaults.
     $item += array(
       '_pid' => 0,
-      '_depth' => $item['_visible'] ? $depth : $number_parts,
+      '_depth' => $depth,
       '_parents' => $parents,
       '_has_children' => 0,
+      '_parent_parts' => $parent_parts,
+      '_slashes' => $slashes,
     );
-    $sort[$path] = $item['_depth'] . sprintf('%05d', $item['weight']) . $item['title'];
+    $sort[$path] = ($item['_visible'] ? $depth : $number_parts) . sprintf('%05d', $item['weight']) . $item['title'];
     unset($item);
   }
   array_multisort($sort, $menu);
-  // Third pass: calculate ancestors, vancode and store into the database.
+  // We are now sorted, so let's build the tree.
+  $children = array();
+  foreach ($menu as $path => $item) {
+    if ($item['_visible']) {
+      $slashes = $item['_slashes'];
+      $parent_parts = $item['_parent_parts'];
+      for ($i = $slashes; $i; $i--) {
+        $parent_path = implode('/', array_slice($parent_parts, 0, $i));
+        if (isset($menu[$parent_path]) && $menu[$parent_path]['_visible']) {
+          $menu[$parent_path]['_children'][] = $path;
+        }
+      }
+    }
+  }
+  // Calculate the nested set values.
+  foreach ($menu as $path => $item) {
+    if ($item['_visible'] && !$item['_pid']) {
+      _menu_renumber($menu, $path);
+    }
+  }
+  // Apply inheritance rules.
   foreach ($menu as $path => $item) {
     $item = &$menu[$path];
     for ($i = $item['_number_parts'] - 1; $i; $i--) {
@@ -650,28 +729,17 @@
       }
     }
     if (!isset($item['access callback'])) {
-      $menu[$path]['access callback'] = isset($item['access arguments']) ? 'user_access' : 0;
+      $item['access callback'] = isset($item['access arguments']) ? 'user_access' : 0;
     }
     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 .'.';
-    }
-    else {
-      $vancode = '';
-    }
     if ($item['_tab']) {
       if (!isset($item['parent'])) {
         $item['parent'] = implode('/', array_slice($item['_parts'], 0, $item['_number_parts'] - 1));
       }
       else {
-        $item['_depth'] = $item['parent'] ? $menu[$item['parent']]['_depth'] + 1 : 1;
+        $item['_depth'] = $item['parent'] ? $menu[$menu_path_map[$item['parent']]]['_depth'] + 1 : 1;
       }
     }
     else {
@@ -679,32 +747,57 @@
       // stored in parents, parent stores the tab parent.
       $item['parent'] = $path;
     }
-    $insert_item = $item + array(
+    $insert_item = $item;
+    unset($item);
+    $item = $insert_item + array(
       'access arguments' => array(),
       'access callback' => '',
       'page arguments' => array(),
       'page callback' => '',
+      '_mleft' => 0,
+      '_mright' => 0,
     );
     db_query("INSERT INTO {menu} (
       mid, pid, path, load_functions, to_arg_functions,
       access_callback, access_arguments, page_callback, page_arguments, fit,
-      number_parts, vancode, visible, parents, depth, has_children, tab, title, parent, type)
-      VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, '%s', %d, %d, %d, '%s', '%s', '%s')",
-      $insert_item['_mid'], $insert_item['_pid'], $path,
-      $insert_item['load_functions'], $insert_item['to_arg_functions'],
-      $insert_item['access callback'], serialize($insert_item['access arguments']),
-      $insert_item['page callback'], serialize($insert_item['page arguments']),
-      $insert_item['_fit'], $insert_item['_number_parts'], $vancode .'+',
-      $insert_item['_visible'], $insert_item['_parents'], $insert_item['_depth'],
-      $insert_item['_has_children'], $item['_tab'], $insert_item['title'],
-      $insert_item['parent'], $insert_item['type']);
-    unset($item);
+      number_parts, visible, parents, depth, has_children, tab, title, parent,
+      type, mleft, mright)
+      VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d,
+      '%s', %d, %d, %d, '%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'], !empty($item['_children']), $item['_tab'],
+      $item['title'], $item['parent'], $item['type'], $item['_mleft'],
+      $item['_mright']);
+    if ($item['_visible']) {
+      if (($item['access callback'] == 'user_access' && count($item['access arguments'] == 1)) || isset($item['permission'])) {
+        $permission = isset($item['permission']) ? $item['permission'] : $item['access arguments'][0];
+        if (isset($permissions[$permission])) {
+          foreach ($permissions[$permission] as $rid) {
+            db_query('INSERT INTO {menu_roles} (mid, rid) VALUES (%d, %d)', $item['_mid'], $rid);
+          }
+        }
+      }
+      else {
+        // The item needs dynamic access handling.
+        db_query('INSERT INTO {menu_roles} (mid, rid) VALUES (%d, %d)', $item['_mid'], 0);
+      }
+    }
   }
 }
 
-function menu_map($arg, $function, $index, $default = FALSE) {
-  $arg[$index] = is_numeric($arg[$index]) ? $function($arg[$index]) : $default;
-  return $arg[$index] ? $arg : FALSE;
+function _menu_renumber(&$menu, $path) {
+  static $counter = 1;
+  if (!isset($menu[$path]['_mleft'])) {
+    $menu[$path]['_mleft'] = $counter++;
+    foreach ($menu[$path]['_children'] as $child_path) {
+      _menu_renumber($menu, $child_path);
+    }
+    $menu[$path]['_mright'] = $counter++;
+  }
 }
 
 // Placeholders.
@@ -741,7 +834,7 @@
         continue;
       }
       // This loads all the tabs.
-      $result = db_query("SELECT * FROM {menu} WHERE parent = '%s' AND tab = 1 ORDER BY vancode", $parent);
+      $result = db_query("SELECT * FROM {menu} WHERE parent = '%s' AND tab = 1 ORDER BY mleft", $parent);
       $tabs_current = '';
       while ($item = db_fetch_object($result)) {
         // This call changes the path from for example user/% to user/123 and
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.79
diff -u -r1.79 system.install
--- modules/system/system.install	12 Feb 2007 17:47:07 -0000	1.79
+++ modules/system/system.install	26 Feb 2007 20:59:49 -0000
@@ -327,8 +327,8 @@
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       db_query("CREATE TABLE {menu} (
-        mid int NOT NULL default '0',
-        pid int NOT NULL default '0',
+        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 '',
@@ -336,24 +336,43 @@
         access_arguments text,
         page_callback varchar(255) NOT NULL default '',
         page_arguments text,
-        fit int NOT NULL default '0',
-        number_parts int NOT NULL default '0',
-        vancode varchar(255) NOT NULL default '',
-        visible int NOT NULL default '0',
+        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',
+        depth int NOT NULL default 0,
+        has_children int NOT NULL default 0,
         tab int NOT NULL default 0,
         title varchar(255) NOT NULL default '',
         parent varchar(255) NOT NULL default '',
         type int NOT NULL default 0,
-        PRIMARY KEY  (path),
-        KEY vancode (vancode),
-        KEY fit (fit),
-        KEY visible (visible),
-        KEY pid (pid),
-        KEY parent (parent)
-      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
+        PRIMARY KEY (path)
+      )");
+
+      db_query("CREATE INDEX {menu}_fit_idx ON {menu} (fit)");
+      db_query("CREATE INDEX {menu}_visible_idx ON {menu} (visible)");
+      db_query("CREATE INDEX {menu}_parent_idx ON {menu} (parent)");
+      db_query("CREATE INDEX {menu}_pid_idx ON {menu} (parent)");
+
+      db_query("CREATE TABLE {menu_roles} (
+        mid int NOT NULL default 0,
+        rid int NOT NULL default 0
+      )");
+
+      db_query("CREATE INDEX {menu_roles}_mid_idx ON {menu_roles} (mid)");
+      db_query("CREATE INDEX {menu_roles}_rid_idx ON {menu_roles} (rid)");
+
+      db_query("CREATE TABLE {menu_roles_cache} (
+        page_mid int NOT NULL default 0,
+        mid int NOT NULL default 0,
+        rid int NOT NULL default 0
+      )");
+
+      db_query("CREATE INDEX {menu_roles_cache}_page_mid_idx ON {menu_roles_cache} (page_mid)");
+      db_query("CREATE INDEX {menu_roles_cache}_mid_idx ON {menu_roles_cache} (mid)");
+      db_query("CREATE INDEX {menu_roles_cache}_rid_idx ON {menu_roles_cache} (rid)");
 
       db_query("CREATE TABLE {node} (
         nid int unsigned NOT NULL auto_increment,
@@ -613,6 +632,7 @@
       ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
 
       break;
+
     case 'pgsql':
       /* create unsigned types */
       db_query("CREATE DOMAIN int_unsigned integer CHECK (VALUE >= 0)");
@@ -801,8 +821,8 @@
       )");
 
       db_query("CREATE TABLE {menu} (
-        mid int NOT NULL default '0',
-        pid int NOT NULL default '0',
+        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 '',
@@ -810,13 +830,14 @@
         access_arguments text,
         page_callback varchar(255) NOT NULL default '',
         page_arguments text,
-        fit int NOT NULL default '0',
-        number_parts int NOT NULL default '0',
-        vancode varchar(255) NOT NULL default '',
-        visible int NOT NULL default '0',
+        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',
+        depth int NOT NULL default 0,
+        has_children int NOT NULL default 0,
         tab int NOT NULL default 0,
         title varchar(255) NOT NULL default '',
         parent varchar(255) NOT NULL default '',
@@ -824,12 +845,37 @@
         PRIMARY KEY (path)
       )");
 
-      db_query("CREATE INDEX {menu}_vancode_idx ON {menu} (vancode)");
       db_query("CREATE INDEX {menu}_fit_idx ON {menu} (fit)");
       db_query("CREATE INDEX {menu}_visible_idx ON {menu} (visible)");
       db_query("CREATE INDEX {menu}_parent_idx ON {menu} (parent)");
       db_query("CREATE INDEX {menu}_pid_idx ON {menu} (parent)");
 
+      db_query("CREATE TABLE {menu_roles} (
+        mid int NOT NULL default 0,
+        rid int NOT NULL default 0
+      )");
+
+      db_query("CREATE INDEX {menu_roles}_mid_idx ON {menu_roles} (mid)");
+      db_query("CREATE INDEX {menu_roles}_rid_idx ON {menu_roles} (rid)");
+
+      db_query("CREATE TABLE {menu_roles_cache} (
+        page_mid int NOT NULL default 0,
+        mid int NOT NULL default 0,
+        rid int NOT NULL default 0
+      )");
+
+      db_query("CREATE INDEX {menu_roles_cache}_page_mid_idx ON {menu_roles_cache} (page_mid)");
+      db_query("CREATE INDEX {menu_roles_cache}_mid_idx ON {menu_roles_cache} (mid)");
+      db_query("CREATE INDEX {menu_roles_cache}_rid_idx ON {menu_roles_cache} (rid)");
+
+      db_query("CREATE TABLE {menu_roles} (
+        mid int NOT NULL default 0,
+        rid int NOT NULL default 0
+      )");
+
+      db_query("CREATE INDEX {menu_roles}_mid_idx ON {menu_roles} (mid)");
+      db_query("CREATE INDEX {menu_roles}_rid_idx ON {menu_roles} (rid)");
+
       db_query("CREATE TABLE {node} (
         nid serial CHECK (nid >= 0),
         vid int_unsigned NOT NULL default '0',
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.756
diff -u -r1.756 user.module
--- modules/user/user.module	15 Feb 2007 11:40:18 -0000	1.756
+++ modules/user/user.module	26 Feb 2007 21:00:00 -0000
@@ -735,6 +735,7 @@
     'page callback' => 'drupal_get_form',
     'page arguments' => array('user_login'),
     'access callback' => 'user_is_anonymous',
+    'type' => MENU_CALLBACK,
   );
 
   $items['user/login'] = array(
