Commit 196aaa7d authored by webchick's avatar webchick

#553944 follow-up by David_Rothstein: Allow modules to override per-page custom themes.

parent 12ed4706
......@@ -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;
......
......@@ -41,12 +41,28 @@
* 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;
......
......@@ -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;
......
......@@ -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,10 +111,33 @@ 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().
*/
......
......@@ -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.';
}
......@@ -236,6 +236,21 @@ function menu_test_theme_callback($argument) {
// causes the page to correctly fall back on using the main site theme.
}
/**
* 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.
......
......@@ -1187,6 +1187,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.
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment