diff -urp includes/menu.inc includes/menu.inc
--- includes/menu.inc	2008-12-03 16:38:59.000000000 +0200
+++ includes/menu.inc	2008-12-08 21:32:07.000000000 +0200
@@ -488,22 +488,33 @@ function _menu_load_objects(&$item, &$ma
  *   $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
  */
 function _menu_check_access(&$item, $map) {
-  // Determine access callback, which will decide whether or not the current
-  // user has access to this path.
-  $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
-  // Check for a TRUE or FALSE value.
-  if (is_numeric($callback)) {
-    $item['access'] = (bool)$callback;
-  }
-  else {
-    $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') {
-      $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
+  $callbacks = array();
+  foreach (unserialize($item['access_arguments']) as $callback) {
+    $callbacks[] = array(
+      'callback' => empty($callback['callback']) ? 0 : trim($callback['callback']),
+       'arguments' => $callback['arguments'],
+     );
+   }
+   foreach ($callbacks as $callback) {
+    // Check for a TRUE or FALSE value.
+    if (is_numeric($callback['callback'])) {
+      $item['access'] =  (bool)$callback['callback'];
     }
     else {
-      $item['access'] = call_user_func_array($callback, $arguments);
+      $arguments = menu_unserialize($callback['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['callback'] == 'user_access') {
+        $item['access'] = count($arguments) == 1 ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]);
+      }
+      else {
+        $item['access'] = call_user_func_array($callback['callback'], $arguments);
+      }
+    }
+    // If menu has multiple access and one access is FALSE then deny access
+    // and stop checking other access callbacks.
+    if ($item['access'] == FALSE) {
+      break;
     }
   }
 }
@@ -535,6 +546,7 @@ function _menu_check_access(&$item, $map
  */
 function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
   $callback = $item['title_callback'];
+  $arguments = menu_unserialize($item['title_arguments'], $map);
   $item['localized_options'] = $item['options'];
   // If we are not doing link translation or if the title matches the
   // link title of its router item, localize it.
@@ -542,20 +554,12 @@ function _menu_item_localize(&$item, $ma
     // t() is a special case. Since it is used very close to all the time,
     // we handle it directly instead of using indirect, slower methods.
     if ($callback == 't') {
-      if (empty($item['title_arguments'])) {
-        $item['title'] = t($item['title']);
-      }
-      else {
-        $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));
+      if (!empty($arguments[0])){
+        $item['title'] = t($arguments[0]);
       }
     }
     elseif (drupal_function_exists($callback)) {
-      if (empty($item['title_arguments'])) {
-        $item['title'] = $callback($item['title']);
-      }
-      else {
-        $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map));
-      }
+      $item['title'] = call_user_func_array($callback, $arguments);
       // Avoid calling check_plain again on l() function.
       if ($callback == 'check_plain') {
         $item['localized_options']['html'] = TRUE;
@@ -856,11 +860,9 @@ function menu_tree_all_data($menu_name =
       $query->fields('m', array(
         'load_functions',
         'to_arg_functions',
-        'access_callback',
         'access_arguments',
         'page_callback',
         'page_arguments',
-        'title',
         'title_callback',
         'title_arguments',
         'type',
@@ -1026,11 +1028,9 @@ function menu_tree_page_data($menu_name 
         $query->fields('m', array(
           'load_functions',
           'to_arg_functions',
-          'access_callback',
           'access_arguments',
           'page_callback',
           'page_arguments',
-          'title',
           'title_callback',
           'title_arguments',
           'type',
@@ -1421,7 +1421,6 @@ function menu_local_tasks($level = 0, $r
       ->fields('menu_router')
       ->condition('tab_root', $router_item['tab_root'])
       ->orderBy('weight')
-      ->orderBy('title')
       ->execute();
     $map = arg();
     $children = array();
@@ -1687,7 +1686,6 @@ function menu_get_active_breadcrumb() {
   $item = menu_get_item();
   if ($item && $item['access']) {
     $active_trail = menu_get_active_trail();
-
     foreach ($active_trail as $parent) {
       $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']);
     }
@@ -2405,7 +2403,32 @@ function _menu_router_build($callbacks) 
   // First pass: separate callbacks from paths, making paths ready for
   // matching. Calculate fitness, and fill some default values.
   $menu = array();
-  foreach ($callbacks as $path => $item) {
+  // We pass $item by reference, as we will populate it with the
+  // grouped callbacks.
+  foreach ($callbacks as $path => &$item) {
+    /******************************************************/
+    // Temporary, just until core's hook_menu() are updated.
+    $item += array(
+      'access' => array(
+        array(
+          'callback' => isset($item['access callback']) ? $item['access callback'] : NULL,
+          'arguments' => isset($item['access arguments']) ? $item['access arguments'] : NULL,
+        ),
+      ),
+      'page' => array(
+        'callback' => isset($item['page callback']) ? $item['page callback'] : NULL,
+        'arguments' => isset($item['page arguments']) ? $item['page arguments'] : NULL,
+      ),
+      'block' => array(
+        'callback' => isset($item['block callback']) ? $item['block callback'] : NULL,
+      ),
+      'load' => array(
+        'arguments' => isset($item['load arguments']) ? $item['load arguments'] : NULL,
+      ),
+    );
+
+    /******************************************************/
+
     $load_functions = array();
     $to_arg_functions = array();
     $fit = 0;
@@ -2438,7 +2461,7 @@ function _menu_router_build($callbacks) 
             // Create an array of arguments that will be passed to the _load
             // function when this menu path is checked, if 'load arguments'
             // exists.
-            $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function;
+            $load_functions[$k] = isset($item['load']['arguments']) ? array($function => $item['load']['arguments']) : $function;
             $match = TRUE;
           }
         }
@@ -2483,7 +2506,6 @@ function _menu_router_build($callbacks) 
     }
   }
   array_multisort($sort, SORT_NUMERIC, $menu);
-
   if (!$menu) {
     return array();
   }
@@ -2496,7 +2518,6 @@ function _menu_router_build($callbacks) 
       'path',
       'load_functions',
       'to_arg_functions',
-      'access_callback',
       'access_arguments',
       'page_callback',
       'page_arguments',
@@ -2504,7 +2525,6 @@ function _menu_router_build($callbacks) 
       'number_parts',
       'tab_parent',
       'tab_root',
-      'title',
       'title_callback',
       'title_arguments',
       'type',
@@ -2524,9 +2544,8 @@ function _menu_router_build($callbacks) 
     for ($i = $item['_number_parts'] - 1; $i; $i--) {
       $parent_path = implode('/', array_slice($item['_parts'], 0, $i));
       if (isset($menu[$parent_path])) {
-
+        // Get the parent item if inheritance rules should be applied.
         $parent = $menu[$parent_path];
-
         if (!isset($item['tab_parent'])) {
           // Parent stores the parent of the path.
           $item['tab_parent'] = $parent_path;
@@ -2534,43 +2553,97 @@ function _menu_router_build($callbacks) 
         if (!isset($item['tab_root']) && !$parent['_tab']) {
           $item['tab_root'] = $parent_path;
         }
+        // A parent was found so we can break.
+        break;
+      }
+    }
+
+    // Process the access callbacks, which might have multiple callbacks.
+    if (!empty($item['access'])) {
+      // Pass by reference as we might change the callback itself.
+      foreach ($item['access'] as $key => &$callback) {
+
         // If an access callback is not found for a default local task we use
         // the callback from the parent, since we expect them to be identical.
         // In all other cases, the access parameters must be specified.
-        if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) {
-          $item['access callback'] = $parent['access callback'];
-          if (!isset($item['access arguments']) && isset($parent['access arguments'])) {
-            $item['access arguments'] = $parent['access arguments'];
+        if (isset($callback['callback'])) {
+          if (is_bool($callback['callback'])) {
+            $callback['callback'] = intval($callback['callback']);
           }
         }
-        // Same 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'];
-          }
+        elseif ($item['type'] == MENU_DEFAULT_LOCAL_TASK && isset($parent['access'][$key]['callback'])) {
+          // Set to parent access callback.
+          // We assume the parent's access callback is in the same
+          // callback location as the item.
+          $callback['callback'] = $parent['access'][$key]['callback'];
+          // If also the arguments are missing set the parent's
+          // arguments.
+          $callback['arguments'] = isset($callback['arguments']) ? $callback['arguments'] : $parent['access'][$key]['arguments'];
+        }
+        elseif (isset($callback['arguments'])) {
+          // Default access callback.
+          $callback['callback'] = 'user_access';
+        }
+        // Serialize access arguments as it will later be menu_unserialize().
+        if (isset($callback['arguments'])) {
+          $callback['arguments'] = is_array($callback['arguments']) ? serialize($callback['arguments']) : $callback['arguments'];
+        }
+        else {
+          $callback['arguments'] = serialize(array());
         }
       }
     }
-    if (!isset($item['access callback']) && isset($item['access arguments'])) {
-      // Default callback.
-      $item['access callback'] = 'user_access';
+
+    // Set parent inheritance for page callbacks.
+    if (!isset($item['page']['callback']) && isset($parent['page']['callback'])) {
+      $item['page']['callback'] = $parent['page']['callback'];
+      if (!isset($item['page']['arguments'])) {
+        $item['page']['arguments'] = isset($parent['page']['arguments']) ? $parent['page']['arguments'] : array();
+      }
+    }
+
+    // Serialize page arguments as it will later be menu_unserialize().
+    if (isset($item['page']['arguments'])) {
+      $item['page']['arguments'] = is_array($item['page']['arguments']) ? serialize($item['page']['arguments']) : $item['page']['arguments'];
+    }
+    else {
+      $item['page']['arguments'] = serialize(array());
+    }
+
+    // Assign block default values.
+    $item['block']['callback'] = isset($item['block']['callback']) ? $item['block']['callback'] : '';
+
+    // Assign load default values.
+    $item['load']['arguments'] = isset($item['load']['arguments']) ? $item['load']['arguments'] : array();
+
+    // For API simplification we allow defining 'title' => 'foo' where t() is
+    // the default callback.
+
+    if (empty($item['title'])) {
+      // We will need to revert the title back to this value.
+      $original_title = isset($item['title']['arguments']) ? $item['title']['arguments'] : '';
+
+      $item['title'] = array(
+        'callback' => isset($item['title callback']) ? $item['title callback'] : 't',
+        'arguments' => isset($item['title arguments']) ? $item['title arguments'] : '',
+      );
     }
-    if (!isset($item['access callback']) || empty($item['page callback'])) {
-      $item['access callback'] = 0;
+    else {
+      $original_title = $item['title'];
+      $item['title'] = array(
+        'callback' => 't',
+        'arguments' => array($item['title']),
+      );
     }
-    if (is_bool($item['access callback'])) {
-      $item['access callback'] = intval($item['access callback']);
+
+    // Make sure there is atleast a single default value assigned to
+    // access callback.
+    if (empty($item['access']) || empty($item['page'])) {
+      $item['access'][0]['callback'] = 0;
     }
 
+    // Assign default values.
     $item += array(
-      'access arguments' => array(),
-      'access callback' => '',
-      'page arguments' => array(),
-      'page callback' => '',
-      'block callback' => '',
-      'title arguments' => array(),
-      'title callback' => 't',
       'description' => '',
       'position' => '',
       'tab_parent' => '',
@@ -2583,23 +2656,24 @@ function _menu_router_build($callbacks) 
       'path' => $item['path'],
       'load_functions' => $item['load_functions'],
       'to_arg_functions' => $item['to_arg_functions'],
-      'access_callback' => $item['access callback'],
-      'access_arguments' => serialize($item['access arguments']),
-      'page_callback' => $item['page callback'],
-      'page_arguments' => serialize($item['page arguments']),
+      'access_arguments' => serialize($item['access']),
+      'page_callback' => $item['page']['callback'],
+      'page_arguments' => $item['page']['arguments'],
       'fit' => $item['_fit'],
       'number_parts' => $item['_number_parts'],
       'tab_parent' => $item['tab_parent'],
       'tab_root' => $item['tab_root'],
-      'title' => $item['title'],
-      'title_callback' => $item['title callback'],
-      'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''),
+      'title_callback' => $item['title']['callback'],
+      'title_arguments' => serialize($item['title']['arguments']),
       'type' => $item['type'],
-      'block_callback' => $item['block callback'],
+      'block_callback' => $item['block']['callback'],
       'description' => $item['description'],
       'position' => $item['position'],
       'weight' => $item['weight'],
     ));
+
+    // Reset title back as it will be needed later.
+    $item['title'] = $original_title;
   }
   // Execute insert object.
   $insert->execute();
diff -urp modules/book/book.module modules/book/book.module
--- modules/book/book.module	2008-12-06 00:18:44.000000000 +0200
+++ modules/book/book.module	2008-12-08 21:22:59.000000000 +0200
@@ -1164,7 +1164,7 @@ function book_menu_subtree_data($item) {
       $menu_router_alias = $query->join('menu_router', 'm', 'm.path = ml.router_path');
       $book_alias = $query->join('book', 'b', 'ml.mlid = b.mlid');
       $query->fields($book_alias);
-      $query->fields($menu_router_alias, array('load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'title', 'title_callback', 'title_arguments', 'type'));
+      $query->fields($menu_router_alias, array('load_functions', 'to_arg_functions', 'access_arguments', 'page_callback', 'page_arguments', 'title_callback', 'title_arguments', 'type'));
       $query->fields('ml');
       $query->condition('menu_name', $item['menu_name']);
       for ($i = 1; $i <= MENU_MAX_DEPTH && $item["p$i"]; ++$i) {
diff -urp modules/menu/menu.admin.inc modules/menu/menu.admin.inc
--- modules/menu/menu.admin.inc	2008-11-15 10:23:07.000000000 +0200
+++ modules/menu/menu.admin.inc	2008-12-08 21:23:22.000000000 +0200
@@ -29,7 +29,7 @@ function menu_overview_page() {
 function menu_overview_form(&$form_state, $menu) {
   global $menu_admin;
   $sql = "
-    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+    SELECT m.load_functions, m.to_arg_functions, m.access_arguments, m.page_callback, m.page_arguments, m.title_callback, m.title_arguments, m.type, m.description, ml.*
     FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
     WHERE ml.menu_name = '%s'
     ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
diff -urp modules/system/system.install modules/system/system.install
--- modules/system/system.install	2008-12-03 18:32:22.000000000 +0200
+++ modules/system/system.install	2008-12-08 21:20:56.000000000 +0200
@@ -758,15 +758,8 @@ function system_schema() {
         'type' => 'text',
         'not null' => TRUE,
       ),
-      'access_callback' => array(
-        'description' => 'The callback which determines the access to this router path. Defaults to user_access.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
       'access_arguments' => array(
-        'description' => 'A serialized array of arguments for the access callback.',
+        'description' => 'A serialized array of access callbacks and arguments.',
         'type' => 'text',
         'not null' => FALSE,
       ),
@@ -809,13 +802,6 @@ function system_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
-      'title' => array(
-        'description' => 'The title for the current page, or the title for the tab if this is a local task.',
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
       'title_callback' => array(
         'description' => 'A function which will alter the title. Defaults to t()',
         'type' => 'varchar',
@@ -3143,6 +3129,20 @@ function system_update_7015() {
 }
 
 /**
+ * Change the access callback and arguments schema, to allow multiple access
+ * callbacks.
+ */
+function system_update_7016() {
+  $ret = array();
+  db_drop_field($ret, 'menu_router', 'access_callback');
+  db_drop_field($ret, 'menu_router', 'title');
+  
+  // Change the description of access_arguments.
+  db_change_field($ret, 'menu_router', 'access_arguments', 'access_arguments', array('description' => 'A serialized array of access callbacks and arguments.', 'type' => 'text', 'not null' => FALSE,));
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
diff -urp modules/system/system.module modules/system/system.module
--- modules/system/system.module	2008-11-28 11:25:59.000000000 +0200
+++ modules/system/system.module	2008-12-08 21:24:48.000000000 +0200
@@ -895,7 +895,7 @@ function system_admin_menu_block($item) 
     $item += db_fetch_array(db_query("SELECT mlid, menu_name FROM {menu_links} ml WHERE ml.router_path = '%s' AND module = 'system'", $item['path']));
   }
   $result = db_query("
-    SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
+    SELECT m.load_functions, m.to_arg_functions, m.access_arguments, m.page_callback, m.page_arguments, m.title_callback, m.title_arguments, m.type, m.description, ml.*
     FROM {menu_links} ml
     LEFT JOIN {menu_router} m ON ml.router_path = m.path
     WHERE ml.plid = %d AND ml.menu_name = '%s' AND hidden = 0", $item['mlid'], $item['menu_name']);
@@ -1465,7 +1465,7 @@ function system_get_module_admin_tasks($
 
   if (!isset($items)) {
     $result = db_query("
-       SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, ml.*
+       SELECT m.load_functions, m.to_arg_functions, m.access_arguments, m.page_callback, m.page_arguments, m.title_callback, m.title_arguments, m.type, ml.*
        FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.link_path LIKE 'admin/%' AND hidden >= 0 AND module = 'system' AND m.number_parts > 2");
     $items = array();
     while ($item = db_fetch_array($result)) {
