Index: includes/menu.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/menu.inc,v
retrieving revision 1.419
diff -u -p -r1.419 menu.inc
--- includes/menu.inc	15 Nov 2010 08:29:03 -0000	1.419
+++ includes/menu.inc	16 Nov 2010 04:19:06 -0000
@@ -1631,15 +1631,14 @@ function menu_get_custom_theme($initiali
     if (!empty($custom_themes)) {
       $custom_theme = array_pop($custom_themes);
     }
-    // Otherwise, execute the theme callback function for the current page, if
-    // there is one, in order to determine the custom theme to set.
-    if (!isset($custom_theme)) {
-      $router_item = menu_get_item();
-      if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) {
-        $theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
-        if (drupal_theme_access($theme_name)) {
-          $custom_theme = $theme_name;
-        }
+    // If there is a theme callback function for the current page, execute it.
+    // If this returns a valid theme, it will override any theme that was set
+    // by a hook_custom_theme() implementation above.
+    $router_item = menu_get_item();
+    if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) {
+      $theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
+      if (drupal_theme_access($theme_name)) {
+        $custom_theme = $theme_name;
       }
     }
   }
Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.556
diff -u -p -r1.556 book.module
--- modules/book/book.module	14 Nov 2010 21:04:45 -0000	1.556
+++ modules/book/book.module	16 Nov 2010 04:19:07 -0000
@@ -174,7 +174,6 @@ function book_menu() {
     'page arguments' => array(1),
     'access callback' => '_book_outline_access',
     'access arguments' => array(1),
-    'theme callback' => '_node_custom_theme',
     'type' => MENU_LOCAL_TASK,
     'weight' => 2,
     'file' => 'book.pages.inc',
@@ -185,7 +184,6 @@ function book_menu() {
     'page arguments' => array('book_remove_form', 1),
     'access callback' => '_book_outline_remove_access',
     'access arguments' => array(1),
-    'theme callback' => '_node_custom_theme',
     'file' => 'book.pages.inc',
   );
 
@@ -210,11 +208,13 @@ function _book_outline_remove_access($no
  * Implements hook_admin_paths().
  */
 function book_admin_paths() {
-  $paths = array(
-    'node/*/outline' => TRUE,
-    'node/*/outline/remove' => TRUE,
-  );
-  return $paths;
+  if (variable_get('node_admin_theme')) {
+    $paths = array(
+      'node/*/outline' => TRUE,
+      'node/*/outline/remove' => TRUE,
+    );
+    return $paths;
+  }
 }
 
 /**
Index: modules/help/help.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/help/help.test,v
retrieving revision 1.21
diff -u -p -r1.21 help.test
--- modules/help/help.test	5 Aug 2010 23:53:38 -0000	1.21
+++ modules/help/help.test	16 Nov 2010 04:19:07 -0000
@@ -22,7 +22,7 @@ class HelpTestCase extends DrupalWebTest
     $this->getModuleList();
 
     // Create users.
-    $this->big_user = $this->drupalCreateUser(array('access administration pages', 'administer permissions'));
+    $this->big_user = $this->drupalCreateUser(array('access administration pages', 'view the administration theme', 'administer permissions'));
     $this->any_user = $this->drupalCreateUser(array());
   }
 
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1319
diff -u -p -r1.1319 node.module
--- modules/node/node.module	14 Nov 2010 21:04:45 -0000	1.1319
+++ modules/node/node.module	16 Nov 2010 04:19:07 -0000
@@ -255,16 +255,18 @@ function node_uri($node) {
  * Implements hook_admin_paths().
  */
 function node_admin_paths() {
-  $paths = array(
-    'node/*/edit' => TRUE,
-    'node/*/delete' => TRUE,
-    'node/*/revisions' => TRUE,
-    'node/*/revisions/*/revert' => TRUE,
-    'node/*/revisions/*/delete' => TRUE,
-    'node/add' => TRUE,
-    'node/add/*' => TRUE,
-  );
-  return $paths;
+  if (variable_get('node_admin_theme')) {
+    $paths = array(
+      'node/*/edit' => TRUE,
+      'node/*/delete' => TRUE,
+      'node/*/revisions' => TRUE,
+      'node/*/revisions/*/revert' => TRUE,
+      'node/*/revisions/*/delete' => TRUE,
+      'node/add' => TRUE,
+      'node/add/*' => TRUE,
+    );
+    return $paths;
+  }
 }
 
 /**
@@ -1913,7 +1915,6 @@ function node_menu() {
     'title' => 'Add content',
     'page callback' => 'node_add_page',
     'access callback' => '_node_add_access',
-    'theme callback' => '_node_custom_theme',
     'file' => 'node.pages.inc',
   );
   $items['rss.xml'] = array(
@@ -1960,7 +1961,6 @@ function node_menu() {
     'page arguments' => array(1),
     'access callback' => 'node_access',
     'access arguments' => array('update', 1),
-    'theme callback' => '_node_custom_theme',
     'weight' => 0,
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
@@ -1972,7 +1972,6 @@ function node_menu() {
     'page arguments' => array('node_delete_confirm', 1),
     'access callback' => 'node_access',
     'access arguments' => array('delete', 1),
-    'theme callback' => '_node_custom_theme',
     'weight' => 1,
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_INLINE,
@@ -1984,7 +1983,6 @@ function node_menu() {
     'page arguments' => array(1),
     'access callback' => '_node_revision_access',
     'access arguments' => array(1),
-    'theme callback' => '_node_custom_theme',
     'weight' => 2,
     'type' => MENU_LOCAL_TASK,
     'file' => 'node.pages.inc',
@@ -2004,7 +2002,6 @@ function node_menu() {
     'page arguments' => array('node_revision_revert_confirm', 1),
     'access callback' => '_node_revision_access',
     'access arguments' => array(1, 'update'),
-    'theme callback' => '_node_custom_theme',
     'file' => 'node.pages.inc',
   );
   $items['node/%node/revisions/%/delete'] = array(
@@ -2014,7 +2011,6 @@ function node_menu() {
     'page arguments' => array('node_revision_delete_confirm', 1),
     'access callback' => '_node_revision_access',
     'access arguments' => array(1, 'delete'),
-    'theme callback' => '_node_custom_theme',
     'file' => 'node.pages.inc',
   );
   return $items;
@@ -2050,17 +2046,6 @@ function node_page_title($node) {
   return $node->title;
 }
 
-/**
- * Theme callback for creating and editing nodes.
- */
-function _node_custom_theme() {
-  // Use the administration theme if the site is configured to use it for
-  // nodes.
-  if (variable_get('node_admin_theme')) {
-    return variable_get('admin_theme');
-  }
-}
-
 function node_last_changed($nid) {
   return db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetch()->changed;
 }
Index: modules/shortcut/shortcut.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/shortcut/shortcut.test,v
retrieving revision 1.6
diff -u -p -r1.6 shortcut.test
--- modules/shortcut/shortcut.test	15 Oct 2010 04:21:02 -0000	1.6
+++ modules/shortcut/shortcut.test	16 Nov 2010 04:19:07 -0000
@@ -34,7 +34,7 @@ class ShortcutTestCase extends DrupalWeb
   function setUp() {
     parent::setUp('toolbar', 'shortcut');
     // Create users.
-    $this->admin_user = $this->drupalCreateUser(array('access toolbar', 'administer shortcuts', 'create article content', 'create page content', 'access content overview'));
+    $this->admin_user = $this->drupalCreateUser(array('access toolbar', 'administer shortcuts', 'view the administration theme', 'create article content', 'create page content', 'access content overview'));
     $this->shortcut_user = $this->drupalCreateUser(array('customize shortcut links', 'switch shortcut sets'));
 
     // Create a node.
Index: modules/simpletest/tests/batch.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/batch.test,v
retrieving revision 1.15
diff -u -p -r1.15 batch.test
--- modules/simpletest/tests/batch.test	3 Oct 2010 02:42:25 -0000	1.15
+++ modules/simpletest/tests/batch.test	16 Nov 2010 04:19:07 -0000
@@ -299,12 +299,16 @@ class BatchPageTestCase extends DrupalWe
     // is using a different theme than would normally be used by the batch API.
     variable_set('theme_default', 'bartik');
     variable_set('admin_theme', 'seven');
+    // Log in as an administrator who can see the administrative theme.
+    $admin_user = $this->drupalCreateUser(array('view the administration theme'));
+    $this->drupalLogin($admin_user);
     // Visit an administrative page that runs a test batch, and check that the
     // theme that was used during batch execution (which the batch callback
     // function saved as a variable) matches the theme used on the
     // administrative page.
     $this->drupalGet('admin/batch-test/test-theme');
-    // The stack should contain the name of the the used on the progress page.
+    // The stack should contain the name of the theme used on the progress
+    // page.
     $this->assertEqual(batch_test_stack(), array('seven'), t('A progressive batch correctly uses the theme of the page that started the batch.'));
   }
 }
Index: modules/simpletest/tests/menu.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu.test,v
retrieving revision 1.38
diff -u -p -r1.38 menu.test
--- modules/simpletest/tests/menu.test	15 Oct 2010 04:46:58 -0000	1.38
+++ modules/simpletest/tests/menu.test	16 Nov 2010 04:19:08 -0000
@@ -159,26 +159,35 @@ class MenuRouterTestCase extends DrupalW
   }
 
   /**
-   * Test that the result of hook_custom_theme() overrides the theme callback.
+   * Test that hook_custom_theme() can control the theme of a page.
    */
   function testHookCustomTheme() {
     // Trigger hook_custom_theme() to dynamically request the Stark theme for
     // the requested page.
     variable_set('menu_test_hook_custom_theme_name', 'stark');
+    theme_enable(array('stark'));
 
-    // Request a page whose theme callback returns the Seven theme. Since Stark
-    // is not a currently enabled theme, our above request should be ignored,
-    // and Seven should still be used.
-    $this->drupalGet('menu-test/theme-callback/use-admin-theme');
-    $this->assertText('Custom theme: seven. Actual theme: seven.', t('The result of hook_custom_theme() does not override a theme callback when it returns a theme that is not enabled.'));
-    $this->assertRaw('seven/style.css', t("The Seven theme's CSS appears on the page."));
+    // Visit a page that does not implement a theme callback. The above request
+    // should be honored.
+    $this->drupalGet('menu-test/no-theme-callback');
+    $this->assertText('Custom theme: stark. Actual theme: stark.', t('The result of hook_custom_theme() is used as the theme for the current page.'));
+    $this->assertRaw('stark/layout.css', t("The Stark theme's CSS appears on the page."));
+  }
 
-    // Now enable the Stark theme and request the same page as above. This
-    // time, we expect hook_custom_theme() to prevail.
+  /**
+   * Test that the theme callback wins out over hook_custom_theme().
+   */
+  function testThemeCallbackHookCustomTheme() {
+    // Trigger hook_custom_theme() to dynamically request the Stark theme for
+    // the requested page.
+    variable_set('menu_test_hook_custom_theme_name', 'stark');
     theme_enable(array('stark'));
+
+    // The menu "theme callback" should take precedence over a value set in
+    // hook_custom_theme().
     $this->drupalGet('menu-test/theme-callback/use-admin-theme');
-    $this->assertText('Custom theme: stark. Actual theme: stark.', t('The result of hook_custom_theme() overrides what was set in a theme callback.'));
-    $this->assertRaw('stark/layout.css', t("The Stark theme's CSS appears on the page."));
+    $this->assertText('Custom theme: seven. Actual theme: seven.', t('The result of hook_custom_theme() does not override what was set in a theme callback.'));
+    $this->assertRaw('seven/style.css', t("The Seven theme's CSS appears on the page."));
   }
 
   /**
@@ -576,6 +585,19 @@ class MenuBreadcrumbTestCase extends Dru
     $perms = array_keys(module_invoke_all('permission'));
     $this->admin_user = $this->drupalCreateUser($perms);
     $this->drupalLogin($this->admin_user);
+
+    // This test puts menu links in the Navigation menu and then tests for
+    // their presence on the page, so we need to ensure that the Navigation
+    // block will be displayed in all active themes.
+    db_update('block')
+      ->fields(array(
+        // Use a region that is valid for all themes.
+        'region' => 'content',
+        'status' => 1,
+      ))
+      ->condition('module', 'system')
+      ->condition('delta', 'navigation')
+      ->execute();
   }
 
   /**
Index: modules/simpletest/tests/menu_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/menu_test.module,v
retrieving revision 1.18
diff -u -p -r1.18 menu_test.module
--- modules/simpletest/tests/menu_test.module	15 Oct 2010 04:46:59 -0000	1.18
+++ modules/simpletest/tests/menu_test.module	16 Nov 2010 04:19:09 -0000
@@ -58,6 +58,11 @@ function menu_test_menu() {
     'page arguments' => array(TRUE),
     'access arguments' => array('access content'),
   );
+  $items['menu-test/no-theme-callback'] = array(
+    'title' => 'Page that displays different themes without using a theme callback.',
+    'page callback' => 'menu_test_theme_page_callback',
+    'access arguments' => array('access content'),
+  );
   // Path containing "exotic" characters.
   $path = "menu-test/ -._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
     "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.214
diff -u -p -r1.214 system.api.php
--- modules/system/system.api.php	15 Nov 2010 08:29:03 -0000	1.214
+++ modules/system/system.api.php	16 Nov 2010 04:19:09 -0000
@@ -1049,13 +1049,18 @@ function hook_menu_get_item_alter(&$rout
  *   - "access arguments": An array of arguments to pass to the access callback
  *     function, with path component substitution as described above.
  *   - "theme callback": (optional) A function returning the machine-readable
- *     name of the default theme that will be used to render the page. If this
- *     function is provided, it is expected to return a currently-active theme
- *     on the site (otherwise, the main site theme will be used instead). If no
- *     function is provided, the main site theme will also be used, unless a
- *     value is inherited from a parent menu item. In all cases, the results of
- *     this function can be dynamically overridden for a particular page
- *     request by modules which implement hook_custom_theme().
+ *     name of the theme that will be used to render the page. If not provided,
+ *     the value will be inherited from a parent menu item. If there is no
+ *     theme callback, or if the function does not return the name of a current
+ *     active theme on the site, the theme for this page will be determined by
+ *     either hook_custom_theme() or the default theme instead. As a general
+ *     rule, the use of theme callback functions should be limited to pages
+ *     whose functionality is very closely tied to a particular theme, since
+ *     they can only be overridden by modules which specifically target those
+ *     pages in hook_menu_alter(). Modules implementing more generic theme
+ *     switching functionality (for example, a module which allows the theme to
+ *     be set dynamically based on the current user's role) should use
+ *     hook_custom_theme() instead.
  *   - "theme arguments": An array of arguments to pass to the theme callback
  *     function, with path component substitution as described above.
  *   - "file": A file that will be included before the page callback is called;
@@ -2050,10 +2055,12 @@ function hook_theme_registry_alter(&$the
  * Return the machine-readable name of the theme to use for the current page.
  *
  * This hook can be used to dynamically set the theme for the current page
- * request. It overrides the default theme as well as any per-page or
- * per-section theme set by the theme callback function in hook_menu(). This
- * should be used by modules which need to override the theme based on dynamic
- * conditions.
+ * request. It should be used by modules which need to override the theme
+ * based on dynamic conditions (for example, a module which allows the theme to
+ * be set based on the current user's role). The return value of this hook will
+ * be used on all pages except those which have a valid per-page or per-section
+ * theme set via a theme callback function in hook_menu(); the themes on those
+ * pages can only be overridden using hook_menu_alter().
  *
  * Since only one theme can be used at a time, the last (i.e., highest
  * weighted) module which returns a valid theme name from this hook will
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.524
diff -u -p -r1.524 system.install
--- modules/system/system.install	13 Nov 2010 17:40:09 -0000	1.524
+++ modules/system/system.install	16 Nov 2010 04:19:09 -0000
@@ -2918,6 +2918,17 @@ function system_update_7065() {
 }
 
 /**
+ * Allow all users to view the administration theme.
+ */
+function system_update_7066() {
+  // Preserve the site's current behavior of automatically allowing all users
+  // to view the administration theme whenever they have access to an
+  // administrative page.
+  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('view the administration theme'));
+  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('view the administration theme'));
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.989
diff -u -p -r1.989 system.module
--- modules/system/system.module	15 Nov 2010 17:47:50 -0000	1.989
+++ modules/system/system.module	16 Nov 2010 04:19:10 -0000
@@ -229,6 +229,10 @@ function system_permission() {
     'access site in maintenance mode' => array(
       'title' => t('Use the site in maintenance mode'),
     ),
+    'view the administration theme' => array(
+      'title' => t('View the administration theme'),
+      'description' => variable_get('admin_theme') ? '' : t('This is only used when the site is configured to use a separate administration theme on the <a href="@appearance-url">Appearance</a> page.', array('@appearance-url' => url('admin/appearance'))),
+    ),
     'access site reports' => array(
       'title' => t('View site reports'),
     ),
@@ -543,8 +547,6 @@ function system_menu() {
     'page callback' => 'system_admin_menu_block_page',
     'weight' => 9,
     'menu_name' => 'management',
-    'theme callback' => 'variable_get',
-    'theme arguments' => array('admin_theme'),
     'file' => 'system.admin.inc',
   );
   $items['admin/compact'] = array(
@@ -1840,9 +1842,6 @@ function system_init() {
   // Add the CSS for this module. These aren't in system.info, because they
   // need to be in the CSS_SYSTEM group rather than the CSS_DEFAULT group.
   drupal_add_css($path . '/system.base.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
-  if (arg(0) == 'admin' || (variable_get('node_admin_theme', '0') && arg(0) == 'node' && (arg(1) == 'add' || arg(2) == 'edit' || arg(2) == 'delete'))) {
-    drupal_add_css($path . '/system.admin.css', array('group' => CSS_SYSTEM));
-  }
   drupal_add_css($path . '/system.menus.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
   drupal_add_css($path . '/system.messages.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
   drupal_add_css($path . '/system.theme.css', array('group' => CSS_SYSTEM, 'every_page' => TRUE));
@@ -1894,6 +1893,16 @@ function system_add_module_assets() {
 }
 
 /**
+ * Implements hook_custom_theme().
+ */
+function system_custom_theme() {
+  if (user_access('view the administration theme') && path_is_admin(current_path())) {
+    drupal_add_css(drupal_get_path('module', 'system') . '/system.admin.css', array('group' => CSS_SYSTEM));
+    return variable_get('admin_theme');
+  }
+}
+
+/**
  * Implements hook_form_FORM_ID_alter().
  */
 function system_form_user_profile_form_alter(&$form, &$form_state) {
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.150
diff -u -p -r1.150 system.test
--- modules/system/system.test	28 Oct 2010 01:33:41 -0000	1.150
+++ modules/system/system.test	16 Nov 2010 04:19:10 -0000
@@ -1321,7 +1321,7 @@ class SystemThemeFunctionalTest extends 
   function setUp() {
     parent::setUp();
 
-    $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer themes', 'bypass node access'));
+    $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'view the administration theme', 'administer themes', 'bypass node access'));
     $this->drupalLogin($this->admin_user);
     $this->node = $this->drupalCreateNode();
   }
Index: modules/translation/translation.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.module,v
retrieving revision 1.90
diff -u -p -r1.90 translation.module
--- modules/translation/translation.module	14 Nov 2010 21:04:45 -0000	1.90
+++ modules/translation/translation.module	16 Nov 2010 04:19:10 -0000
@@ -65,7 +65,6 @@ function translation_menu() {
     'access arguments' => array(1),
     'type' => MENU_LOCAL_TASK,
     'weight' => 2,
-    'theme callback' => '_node_custom_theme',
     'file' => 'translation.pages.inc',
   );
   return $items;
@@ -89,10 +88,12 @@ function _translation_tab_access($node) 
  * Implements hook_admin_paths().
  */
 function translation_admin_paths() {
-  $paths = array(
-    'node/*/translate' => TRUE,
-  );
-  return $paths;
+  if (variable_get('node_admin_theme')) {
+    $paths = array(
+      'node/*/translate' => TRUE,
+    );
+    return $paths;
+  }
 }
 
 /**
