diff options
-rw-r--r-- | includes/menu.inc | 24 | ||||
-rw-r--r-- | includes/theme.inc | 26 | ||||
-rw-r--r-- | modules/menu/menu.api.php | 11 | ||||
-rw-r--r-- | modules/simpletest/tests/menu.test | 37 | ||||
-rw-r--r-- | modules/simpletest/tests/menu_test.module | 17 | ||||
-rw-r--r-- | modules/system/system.api.php | 25 |
6 files changed, 118 insertions, 22 deletions
diff --git a/includes/menu.inc b/includes/menu.inc index 3bb996243..5362aafd9 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1505,8 +1505,8 @@ function menu_get_active_help() { * * @param $initialize * This parameter should only be used internally; it is set to TRUE in order - * to force the custom theme to be initialized from the menu router item for - * the current page. + * to force the custom theme to be initialized for the current page request. + * * @return * The machine-readable name of the custom theme, if there is one. * @@ -1517,9 +1517,23 @@ function menu_get_custom_theme($initialize = FALSE) { // Skip this if the site is offline or being installed or updated, since the // menu system may not be correctly initialized then. if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) { - $router_item = menu_get_item(); - if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) { - $custom_theme = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']); + // First allow modules to dynamically set a custom theme for the current + // page. Since we can only have one, the last module to return a valid + // theme takes precedence. + $custom_themes = array_filter(module_invoke_all('custom_theme'), 'drupal_theme_access'); + 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; + } + } } } return $custom_theme; diff --git a/includes/theme.inc b/includes/theme.inc index afab19a22..eeb7a5faf 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -41,12 +41,28 @@ define('MARK_UPDATED', 2); * Determines if a theme is available to use. * * @param $theme - * An object representing the theme to check. + * Either the name of a theme or a full theme object. + * * @return * Boolean TRUE if the theme is enabled or is the site administration theme; * FALSE otherwise. */ function drupal_theme_access($theme) { + if (is_object($theme)) { + return _drupal_theme_access($theme); + } + else { + $themes = list_themes(); + return isset($themes[$theme]) && _drupal_theme_access($themes[$theme]); + } +} + +/** + * Helper function for determining access to a theme. + * + * @see drupal_theme_access() + */ +function _drupal_theme_access($theme) { $admin_theme = variable_get('admin_theme'); return !empty($theme->status) || ($admin_theme && $theme->name == $admin_theme); } @@ -67,12 +83,12 @@ function drupal_theme_initialize() { // Only select the user selected theme if it is available in the // list of themes that can be accessed. - $theme = !empty($user->theme) && isset($themes[$user->theme]) && drupal_theme_access($themes[$user->theme]) ? $user->theme : variable_get('theme_default', 'garland'); + $theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : variable_get('theme_default', 'garland'); - // Allow modules to override the present theme... only select custom theme - // if it is available in the list of themes that can be accessed. + // Allow modules to override the theme. Validation has already been performed + // inside menu_get_custom_theme(), so we do not need to check it again here. $custom_theme = menu_get_custom_theme(); - $theme = $custom_theme && isset($themes[$custom_theme]) && drupal_theme_access($themes[$custom_theme]) ? $custom_theme : $theme; + $theme = !empty($custom_theme) ? $custom_theme : $theme; // Store the identifier for retrieving theme settings with. $theme_key = $theme; diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php index 8a39032fb..b8f4335e2 100644 --- a/modules/menu/menu.api.php +++ b/modules/menu/menu.api.php @@ -175,10 +175,13 @@ * - "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 theme that will be used to render the page. If the function - * returns nothing, the main site theme will be used. If no function is - * provided, the main site theme will also be used, unless a value is - * inherited from a parent menu item. + * 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(). * - "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; diff --git a/modules/simpletest/tests/menu.test b/modules/simpletest/tests/menu.test index c98014b06..d9563a86a 100644 --- a/modules/simpletest/tests/menu.test +++ b/modules/simpletest/tests/menu.test @@ -38,7 +38,7 @@ class MenuRouterTestCase extends DrupalWebTestCase { */ function testThemeCallbackAdministrative() { $this->drupalGet('menu-test/theme-callback/use-admin-theme'); - $this->assertText('Requested theme: seven. Actual theme: seven.', t('The administrative theme can be correctly set in a theme callback.')); + $this->assertText('Custom theme: seven. Actual theme: seven.', t('The administrative theme can be correctly set in a theme callback.')); $this->assertRaw('seven/style.css', t("The administrative theme's CSS appears on the page.")); } @@ -47,7 +47,7 @@ class MenuRouterTestCase extends DrupalWebTestCase { */ function testThemeCallbackInheritance() { $this->drupalGet('menu-test/theme-callback/use-admin-theme/inheritance'); - $this->assertText('Requested theme: seven. Actual theme: seven. Theme callback inheritance is being tested.', t('Theme callback inheritance correctly uses the administrative theme.')); + $this->assertText('Custom theme: seven. Actual theme: seven. Theme callback inheritance is being tested.', t('Theme callback inheritance correctly uses the administrative theme.')); $this->assertRaw('seven/style.css', t("The administrative theme's CSS appears on the page.")); } @@ -77,7 +77,7 @@ class MenuRouterTestCase extends DrupalWebTestCase { $admin_user = $this->drupalCreateUser(array('access site in maintenance mode')); $this->drupalLogin($admin_user); $this->drupalGet('menu-test/theme-callback/use-admin-theme'); - $this->assertText('Requested theme: seven. Actual theme: seven.', t('The theme callback system is correctly triggered for an administrator when the site is in maintenance mode.')); + $this->assertText('Custom theme: seven. Actual theme: seven.', t('The theme callback system is correctly triggered for an administrator when the site is in maintenance mode.')); $this->assertRaw('seven/style.css', t("The administrative theme's CSS appears on the page.")); } @@ -87,13 +87,13 @@ class MenuRouterTestCase extends DrupalWebTestCase { function testThemeCallbackOptionalTheme() { // Request a theme that is not enabled. $this->drupalGet('menu-test/theme-callback/use-stark-theme'); - $this->assertText('Requested theme: stark. Actual theme: garland.', t('The theme callback system falls back on the default theme when a theme that is not enabled is requested.')); + $this->assertText('Custom theme: NONE. Actual theme: garland.', t('The theme callback system falls back on the default theme when a theme that is not enabled is requested.')); $this->assertRaw('garland/style.css', t("The default theme's CSS appears on the page.")); // Now enable the theme and request it again. theme_enable(array('stark')); $this->drupalGet('menu-test/theme-callback/use-stark-theme'); - $this->assertText('Requested theme: stark. Actual theme: stark.', t('The theme callback system uses an optional theme once it has been enabled.')); + $this->assertText('Custom theme: stark. Actual theme: stark.', t('The theme callback system uses an optional theme once it has been enabled.')); $this->assertRaw('stark/layout.css', t("The optional theme's CSS appears on the page.")); } @@ -102,7 +102,7 @@ class MenuRouterTestCase extends DrupalWebTestCase { */ function testThemeCallbackFakeTheme() { $this->drupalGet('menu-test/theme-callback/use-fake-theme'); - $this->assertText('Requested theme: fake_theme. Actual theme: garland.', t('The theme callback system falls back on the default theme when a theme that does not exist is requested.')); + $this->assertText('Custom theme: NONE. Actual theme: garland.', t('The theme callback system falls back on the default theme when a theme that does not exist is requested.')); $this->assertRaw('garland/style.css', t("The default theme's CSS appears on the page.")); } @@ -111,11 +111,34 @@ class MenuRouterTestCase extends DrupalWebTestCase { */ function testThemeCallbackNoThemeRequested() { $this->drupalGet('menu-test/theme-callback/no-theme-requested'); - $this->assertText('Requested theme: NONE. Actual theme: garland.', t('The theme callback system falls back on the default theme when no theme is requested.')); + $this->assertText('Custom theme: NONE. Actual theme: garland.', t('The theme callback system falls back on the default theme when no theme is requested.')); $this->assertRaw('garland/style.css', t("The default theme's CSS appears on the page.")); } /** + * Test that the result of hook_custom_theme() overrides the theme callback. + */ + 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'); + + // 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.")); + + // Now enable the Stark theme and request the same page as above. This + // time, we expect hook_custom_theme() to prevail. + theme_enable(array('stark')); + $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.")); + } + + /** * Tests for menu_link_maintain(). */ function testMenuLinkMaintain() { diff --git a/modules/simpletest/tests/menu_test.module b/modules/simpletest/tests/menu_test.module index 194fe8006..1e436ee59 100644 --- a/modules/simpletest/tests/menu_test.module +++ b/modules/simpletest/tests/menu_test.module @@ -203,7 +203,7 @@ function menu_test_theme_page_callback($inherited = FALSE) { // Now check both the requested custom theme and the actual theme being used. $custom_theme = menu_get_custom_theme(); $requested_theme = empty($custom_theme) ? 'NONE' : $custom_theme; - $output = "Requested theme: $requested_theme. Actual theme: $theme_key."; + $output = "Custom theme: $requested_theme. Actual theme: $theme_key."; if ($inherited) { $output .= ' Theme callback inheritance is being tested.'; } @@ -237,6 +237,21 @@ function menu_test_theme_callback($argument) { } /** + * Implement hook_custom_theme(). + * + * @return + * The name of the custom theme to use for the current page. + */ +function menu_test_custom_theme() { + // If an appropriate variable has been set in the database, request the theme + // that is stored there. Otherwise, do not attempt to dynamically set the + // theme. + if ($theme = variable_get('menu_test_hook_custom_theme_name', FALSE)) { + return $theme; + } +} + +/** * Helper function for the testMenuName() test. Used to change the menu_name * parameter of a menu. * diff --git a/modules/system/system.api.php b/modules/system/system.api.php index 78cc24e2a..eefafdd74 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -1188,6 +1188,31 @@ function hook_theme_registry_alter(&$theme_registry) { } /** + * 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. + * + * 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 + * prevail. + * + * @return + * The machine-readable name of the theme that should be used for the current + * page request. The value returned from this function will only have an + * effect if it corresponds to a currently-active theme on the site. + */ +function hook_custom_theme() { + // Allow the user to request a particular theme via a query parameter. + if (isset($_GET['theme'])) { + return $_GET['theme']; + } +} + +/** * Register XML-RPC callbacks. * * This hook lets a module register callback functions to be called when |