diff --git a/core/includes/install.inc b/core/includes/install.inc index db92987..42ec9ac 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -425,7 +425,6 @@ function drupal_install_system() { // Clear out module list and hook implementation statics. system_list_reset(); - module_list_reset(); module_implements_reset(); config_install_default_config('module', 'system'); diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 112e4f5..0091f03 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -1623,6 +1623,9 @@ function theme_menu_link(array $variables) { */ function theme_menu_local_task($variables) { $link = $variables['element']['#link']; + $link += array( + 'localized_options' => array(), + ); $link_text = $link['title']; if (!empty($variables['element']['#active'])) { @@ -1869,8 +1872,8 @@ function menu_local_tasks($level = 0) { $data = &drupal_static(__FUNCTION__); $root_path = &drupal_static(__FUNCTION__ . ':root_path', ''); $empty = array( - 'tabs' => array('count' => 0, 'output' => array()), - 'actions' => array('count' => 0, 'output' => array()), + 'tabs' => array(), + 'actions' => array(), 'root_path' => &$root_path, ); @@ -1922,8 +1925,7 @@ function menu_local_tasks($level = 0) { // Tab parenting may skip levels, so the number of parts in the path may not // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort). $depth = 1001; - $actions['count'] = 0; - $actions['output'] = array(); + $actions = array(); while (isset($children[$path])) { $tabs_current = array(); $actions_current = array(); @@ -1960,6 +1962,7 @@ function menu_local_tasks($level = 0) { '#theme' => 'menu_local_task', '#link' => $link, '#active' => TRUE, + '#weight' => isset($link['weight']) ? $link['weight'] : NULL, ); $next_path = $item['path']; $tab_count++; @@ -1972,6 +1975,7 @@ function menu_local_tasks($level = 0) { $actions_current[] = array( '#theme' => 'menu_local_action', '#link' => $link, + '#weight' => isset($link['weight']) ? $link['weight'] : NULL, ); $action_count++; } @@ -1980,6 +1984,7 @@ function menu_local_tasks($level = 0) { $tabs_current[] = array( '#theme' => 'menu_local_task', '#link' => $link, + '#weight' => isset($link['weight']) ? $link['weight'] : NULL, ); $tab_count++; } @@ -1987,10 +1992,8 @@ function menu_local_tasks($level = 0) { } } $path = $next_path; - $tabs[$depth]['count'] = $tab_count; - $tabs[$depth]['output'] = $tabs_current; - $actions['count'] += $action_count; - $actions['output'] = array_merge($actions['output'], $actions_current); + $tabs[$depth] = $tabs_current; + $actions = array_merge($actions, $actions_current); $depth++; } $data['actions'] = $actions; @@ -2036,6 +2039,7 @@ function menu_local_tasks($level = 0) { '#theme' => 'menu_local_task', '#link' => $link, '#active' => TRUE, + '#weight' => isset($link['weight']) ? $link['weight'] : NULL, ); $next_path = $item['tab_parent']; if (isset($tasks[$next_path])) { @@ -2046,14 +2050,14 @@ function menu_local_tasks($level = 0) { $tabs_current[] = array( '#theme' => 'menu_local_task', '#link' => $link, + '#weight' => isset($link['weight']) ? $link['weight'] : NULL, ); } } } $path = $next_path; $parent = $next_parent; - $tabs[$depth]['count'] = $count; - $tabs[$depth]['output'] = $tabs_current; + $tabs[$depth] = $tabs_current; $depth--; } // Sort by depth. @@ -2075,7 +2079,7 @@ function menu_local_tasks($level = 0) { } // @todo If there are no tabs, then there still can be actions; for example, // when added via hook_menu_local_tasks_alter(). - elseif (!empty($data['actions']['output'])) { + elseif (!empty($data['actions'])) { return array('actions' => $data['actions']) + $empty; } return $empty; @@ -2188,7 +2192,7 @@ function menu_contextual_links($module, $parent_path, $args) { function menu_primary_local_tasks() { $links = menu_local_tasks(0); // Do not display single tabs. - return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : ''); + return count($links['tabs']) > 1 ? $links['tabs'] : ''; } /** @@ -2197,7 +2201,7 @@ function menu_primary_local_tasks() { function menu_secondary_local_tasks() { $links = menu_local_tasks(1); // Do not display single tabs. - return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : ''); + return count($links['tabs']) > 1 ? $links['tabs'] : ''; } /** @@ -2205,7 +2209,7 @@ function menu_secondary_local_tasks() { */ function menu_local_actions() { $links = menu_local_tasks(); - return $links['actions']['output']; + return $links['actions']; } /** @@ -3547,7 +3551,6 @@ function _menu_router_build($callbacks) { $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions); $item += array( 'title' => '', - 'weight' => 0, 'type' => MENU_NORMAL_ITEM, 'module' => '', '_number_parts' => $number_parts, @@ -3555,6 +3558,9 @@ function _menu_router_build($callbacks) { '_fit' => $fit, ); $item += array( + // Default MENU_DEFAULT_LOCAL_TASKs to a weight of -10, so they appear as + // first tab by default. + 'weight' => ($item['type'] & MENU_DEFAULT_LOCAL_TASK) == MENU_DEFAULT_LOCAL_TASK ? -10 : 0, '_visible' => (bool) ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB), '_tab' => (bool) ($item['type'] & MENU_IS_LOCAL_TASK), ); diff --git a/core/includes/module.inc b/core/includes/module.inc index c1d99f2..cd6d500 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -34,7 +34,7 @@ function module_load_all($bootstrap = FALSE, $reset = FALSE) { // Unless $boostrap is NULL, load the requested set of modules. if (isset($bootstrap) && !$has_run) { - $type = $bootstrap ? 'bootstrap' : 'module_enabled'; + $type = $bootstrap ? 'bootstrap' : 'module'; foreach (module_list($type) as $module) { drupal_load('module', $module); } @@ -57,7 +57,7 @@ function module_load_all($bootstrap = FALSE, $reset = FALSE) { * * @param string $type * The type of list to return: - * - module_enabled: All enabled modules. + * - module: All enabled modules. * - bootstrap: All enabled modules required for bootstrap. * @param array $fixed_list * (optional) An array of module names to override the list of modules. This @@ -73,7 +73,7 @@ function module_load_all($bootstrap = FALSE, $reset = FALSE) { * * @see module_list_reset() */ -function module_list($type = 'module_enabled', array $fixed_list = NULL, $reset = FALSE) { +function module_list($type = 'module', array $fixed_list = NULL, $reset = FALSE) { // This static is only used for $fixed_list. It must not be a drupal_static(), // since any call to drupal_static_reset() in unit tests would cause an // attempt to retrieve the list of modules from the database (which does not @@ -120,13 +120,13 @@ function module_list_reset() { * * @param $type * The type of list to return: - * - module_enabled: All enabled modules. + * - module: All enabled modules. * - bootstrap: All enabled modules required for bootstrap. - * - theme: All themes. + * - theme: All enabled themes. * * @return * An associative array of modules or themes, keyed by name. For $type - * 'bootstrap' and 'module_enabled', the array values equal the keys. + * 'bootstrap' and 'module', the array values equal the keys. * For $type 'theme', the array values are objects representing the * respective database row, with the 'info' property already unserialized. * @@ -167,13 +167,13 @@ function system_list($type) { $lists['bootstrap'] = array_keys($bootstrap_list); } // Otherwise build the list for enabled modules and themes. - elseif (!isset($lists['module_enabled'])) { + elseif (!isset($lists['module'])) { if ($cached = cache('bootstrap')->get('system_list')) { $lists = $cached->data; } - else { + if (!isset($lists[$type])) { $lists = array( - 'module_enabled' => array(), + 'module' => array(), 'theme' => array(), 'filepaths' => array(), ); @@ -182,11 +182,11 @@ function system_list($type) { // Drupal installations, which might have modules installed in different // locations in the file system. The ordering here must also be // consistent with the one used in module_implements(). - $enabled_modules = config('system.module')->get('enabled'); + $enabled_modules = (array) config('system.module')->get('enabled'); $module_files = state()->get('system.module.files'); foreach ($enabled_modules as $name => $weight) { // Build a list of all enabled modules. - $lists['module_enabled'][$name] = $name; + $lists['module'][$name] = $name; // Build a list of filenames so drupal_get_filename can use it. $lists['filepaths'][] = array( 'type' => 'module', @@ -196,31 +196,27 @@ function system_list($type) { } // Build a list of themes. - $enabled_themes = config('system.theme')->get('enabled'); - // @todo Themes include all themes, including disabled/uninstalled. This - // system.theme.data state will go away entirely as soon as themes have - // a proper installation status. - // @see http://drupal.org/node/1067408 - $theme_data = state()->get('system.theme.data'); - if (empty($theme_data)) { - // @todo: system_list() may be called from _drupal_bootstrap_code() and + $enabled_themes = (array) config('system.theme')->get('enabled'); + array_walk($enabled_themes, function (&$value, $key) { + $value = 'system.theme.data.' . $key; + }); + $theme_data = (array) state()->getMultiple($enabled_themes); + // @todo Remove me? + if (0 && empty($theme_data)) { + // system_list() may be called from _drupal_bootstrap_code() and // module_load_all(), in which case system.module is not loaded yet. // Prevent a filesystem scan in drupal_load() and include it directly. - // @see http://drupal.org/node/1067408 require_once DRUPAL_ROOT . '/core/modules/system/system.module'; $theme_data = system_rebuild_theme_data(); } foreach ($theme_data as $name => $theme) { - $theme->status = (int) isset($enabled_themes[$name]); $lists['theme'][$name] = $theme; // Build a list of filenames so drupal_get_filename can use it. - if (isset($enabled_themes[$name])) { - $lists['filepaths'][] = array( - 'type' => 'theme', - 'name' => $name, - 'filepath' => $theme->filename, - ); - } + $lists['filepaths'][] = array( + 'type' => 'theme', + 'name' => $name, + 'filepath' => $theme->filename, + ); } // @todo Move into list_themes(). Read info for a particular requested // theme from state instead. @@ -268,17 +264,16 @@ function system_list($type) { function system_list_reset() { drupal_static_reset('system_list'); drupal_static_reset('system_rebuild_module_data'); + drupal_static_reset('system_rebuild_theme_data'); drupal_static_reset('list_themes'); cache('bootstrap')->deleteMultiple(array('bootstrap_modules', 'system_list')); cache()->delete('system_info'); - // Remove last known theme data state. - // This causes system_list() to call system_rebuild_theme_data() on its next - // invocation. When enabling a module that implements hook_system_info_alter() - // to inject a new (testing) theme or manipulate an existing theme, then that - // will cause system_list_reset() to be called, but theme data is not - // necessarily rebuilt afterwards. - // @todo Obsolete with proper installation status for themes. - state()->delete('system.theme.data'); + // Rebuild theme data to update theme states. + // When enabling a module that implements hook_system_info_alter() to inject a + // new (testing) theme or manipulate an existing theme, then that will cause + // system_list_reset() to be called, but theme data is not necessarily rebuilt + // afterwards. + system_rebuild_theme_data(); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 3c4576a..55f63eb 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -55,14 +55,44 @@ */ /** + * Loads a theme. + * + * @param string $name + * The name of a theme to load. + * + * @return + * The loaded theme object for $name, or FALSE if the passed $name does not + * exist or is not enabled. + */ +function theme_load($name) { + $themes = list_themes(); + if (isset($themes[$name]) && drupal_theme_access($themes[$name])) { + return $themes[$name]; + } + return FALSE; +} + +/** + * Menu title callback; Returns the sanitized human-readable name of a theme. + * + * @param $theme + * The theme object to return the title for. + * + * This needs to be a private function to avoid clashing with a possible + * theme_menu_title() theme function by Menu or a potential Menu Title module. + */ +function _theme_menu_title($theme) { + return check_plain($theme->info['name']); +} + +/** * Determines if a theme is available to use. * * @param $theme * 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. + * Boolean TRUE if the theme is enabled; FALSE otherwise. */ function drupal_theme_access($theme) { if (is_object($theme)) { @@ -70,6 +100,9 @@ function drupal_theme_access($theme) { } else { $themes = list_themes(); + if (!isset($themes[$theme])) { + return FALSE; + } return !empty($themes[$theme]->status); } } @@ -661,14 +694,14 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) { * declare this theme as their base theme. */ function list_themes($refresh = FALSE) { - $list = &drupal_static(__FUNCTION__, array()); + $list = &drupal_static(__FUNCTION__); if ($refresh) { - $list = array(); + $list = NULL; system_list_reset(); } - if (empty($list)) { + if (!isset($list)) { $list = array(); $themes = array(); // Extract from the database only when it is available. @@ -1407,29 +1440,134 @@ function theme_render_template($template_file, $variables) { /** * Enable a given list of themes. * - * @param $theme_list - * An array of theme names. + * @param array $theme_list + * An array of theme names to enable. + * @param bool $enable_dependencies + * If TRUE, dependencies will automatically be added and enabled in the + * correct order. This incurs a significant performance cost, so use FALSE + * if you know $theme_list is already complete and in the correct order. + * + * @todo D8: This is almost identical to module_enable() now. Only differences: + * - There is no required default .theme PHP file to load. + * - There is no schema to install. + * - System list, state, theme list, and static resets slightly differ. + * @todo hook_themes_installed(), hook_themes_enabled(), etc are not invoked in + * themes, since they are excluded from the hook system. */ -function theme_enable($theme_list) { - drupal_clear_css_cache(); +function theme_enable($theme_list, $enable_dependencies = TRUE) { + if ($enable_dependencies) { + // Get all theme data so we can find dependencies and sort. + $theme_data = system_rebuild_theme_data(); + // Create an associative array with weights as values. + $theme_list = array_flip(array_values($theme_list)); + + while (list($theme) = each($theme_list)) { + if (!isset($theme_data[$theme])) { + // This theme is not found in the filesystem, abort. + return FALSE; + } + if ($theme_data[$theme]->status) { + // Skip already enabled themes. + unset($theme_list[$theme]); + continue; + } + $theme_list[$theme] = $theme_data[$theme]->sort; + + // Add dependencies to the list, with a placeholder weight. + // The new themes will be processed as the while loop continues. + foreach (array_keys($theme_data[$theme]->requires) as $dependency) { + if (!isset($theme_list[$dependency])) { + $theme_list[$dependency] = 0; + } + } + } + + if (!$theme_list) { + // Nothing to do. All themes already enabled. + return TRUE; + } + + // Sort the theme list by pre-calculated weights. + arsort($theme_list); + $theme_list = array_keys($theme_list); + } + + // Required for schema version checks and constants. + include_once DRUPAL_ROOT . '/core/includes/install.inc'; + + $themes_installed = array(); + $themes_enabled = array(); + $schema_store = drupal_container()->get('keyvalue')->get('system.schema'); $theme_config = config('system.theme'); - $disabled_themes = config('system.theme.disabled'); + $disabled_config = config('system.theme.disabled'); foreach ($theme_list as $key) { - // The value is not used; the weight is ignored for themes currently. - $theme_config->set("enabled.$key", 0); - $disabled_themes->clear($key); - // Install default configuration of the theme. - config_install_default_config('theme', $key); + // Only process themes that are not already enabled. + $enabled = $theme_config->get("enabled.$key") !== NULL; + if (!$enabled) { + // The value is not used; the weight is ignored for themes currently. + $theme_config + ->set("enabled.$key", 0) + ->save(); + $disabled_config + ->clear($key) + ->save(); + + // Refresh the system list to include it. + system_list_reset(); + // Refresh the schema to include it. + drupal_get_schema(NULL, TRUE); + // Update the theme registry to include it. + list_themes(TRUE); + drupal_theme_rebuild(); + + // Allow modules to react prior to the installation of a theme. + module_invoke_all('themes_preinstall', array($key)); + + // Now install the theme if necessary. + if (drupal_get_installed_schema_version($key, TRUE) == SCHEMA_UNINSTALLED) { + $version = SCHEMA_INSTALLED; + + // Install default configuration of the theme. + config_install_default_config('theme', $key); + + drupal_set_installed_schema_version($key, $version); + // Allow the theme to perform install tasks. + $function = $key . '_install'; + if (function_exists($function)) { + $function(); + } + // Record the fact that it was installed. + $themes_installed[] = $key; + watchdog('system', '%theme theme installed.', array('%theme' => $key), WATCHDOG_INFO); + } + + // Allow modules to react prior to the enabling of a theme. + module_invoke_all('themes_preenable', array($key)); + + // Enable the theme. + $function = $key . '_enable'; + if (function_exists($function)) { + $function(); + } + + // Record the fact that it was enabled. + $themes_enabled[] = $key; + watchdog('system', '%theme theme enabled.', array('%theme' => $key), WATCHDOG_INFO); + } } - $theme_config->save(); - $disabled_themes->save(); - list_themes(TRUE); - menu_router_rebuild(); - drupal_theme_rebuild(); + // If any themes were newly installed, invoke hook_themes_installed(). + if (!empty($themes_installed)) { + module_invoke_all('themes_installed', $themes_installed); + } - // Invoke hook_themes_enabled() after the themes have been enabled. - module_invoke_all('themes_enabled', $theme_list); + // If any themes were newly enabled, invoke hook_themes_enabled(). + if (!empty($themes_enabled)) { + module_invoke_all('themes_enabled', $themes_enabled); + } + + drupal_clear_css_cache(); + return TRUE; } /** @@ -1450,17 +1588,18 @@ function theme_disable($theme_list) { drupal_clear_css_cache(); $theme_config = config('system.theme'); - $disabled_themes = config('system.theme.disabled'); + $disabled_config = config('system.theme.disabled'); foreach ($theme_list as $key) { // The value is not used; the weight is ignored for themes currently. - $theme_config->clear("enabled.$key"); - $disabled_themes->set($key, 0); + $disabled_config + ->set($key, 0) + ->save(); + $theme_config + ->clear("enabled.$key") + ->save(); } - $theme_config->save(); - $disabled_themes->save(); list_themes(TRUE); - menu_router_rebuild(); drupal_theme_rebuild(); // Invoke hook_themes_disabled after the themes have been disabled. diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc index 4b3e80c..6718e87 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -17,11 +17,6 @@ function _drupal_maintenance_theme() { global $theme, $theme_key, $conf; - // If $theme is already set, assume the others are set too, and do nothing. - if (isset($theme)) { - return; - } - require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc'); require_once DRUPAL_ROOT . '/core/includes/theme.inc'; require_once DRUPAL_ROOT . '/core/includes/common.inc'; diff --git a/core/includes/update.inc b/core/includes/update.inc index fe8034c..35f1c97 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -259,7 +259,7 @@ function update_prepare_d8_bootstrap() { // Update the environment for the language bootstrap if needed. update_prepare_d8_language(); // Prime the classloader. - system_list('module_enabled'); + system_list('module'); // Change language column to langcode in url_alias. if (db_table_exists('url_alias') && db_field_exists('url_alias', 'language')) { diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 869d4ee..b0a7947 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -45,7 +45,7 @@ public function registerBundles() { // @todo Remove the necessity of calling system_list() to find out which // bundles exist. See http://drupal.org/node/1331486 - $modules = array_keys(system_list('module_enabled')); + $modules = array_keys(system_list('module')); foreach ($modules as $module) { $camelized = ContainerBuilder::camelize($module); $class = "Drupal\\{$module}\\{$camelized}Bundle"; diff --git a/core/lib/Drupal/Core/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php index cb448de..5d6e6f9 100644 --- a/core/lib/Drupal/Core/ExceptionController.php +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -263,9 +263,10 @@ public function on500Html(FlattenException $exception, Request $request) { drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); } - drupal_set_title(t('Error')); // We fallback to a maintenance page at this point, because the page // generation itself can generate errors. + drupal_maintenance_theme(); + drupal_set_title(t('Error')); $output = theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.'))); $response = new Response($output, 500); diff --git a/core/modules/block/block.admin.inc b/core/modules/block/block.admin.inc index 2a17a55..c233ee3 100644 --- a/core/modules/block/block.admin.inc +++ b/core/modules/block/block.admin.inc @@ -33,6 +33,9 @@ function block_admin_display($theme = NULL) { // If theme is not specifically set, rehash for the current theme. $theme = $theme_key; } + elseif (is_object($theme)) { + $theme = $theme->name; + } // Fetch and sort blocks. $blocks = block_admin_display_prepare_blocks($theme); diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 2978146..ced66f4 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -62,11 +62,21 @@ function block_help($path, $arg) { case 'admin/structure/block/add': return '
' . t('Use this page to create a new custom block.') . '
'; } - if ($arg[0] == 'admin' && $arg[1] == 'structure' && $arg['2'] == 'block' && (empty($arg[3]) || $arg[3] == 'list')) { - $demo_theme = !empty($arg[4]) ? $arg[4] : variable_get('theme_default', 'stark'); - $themes = list_themes(); + + if ($arg[0] === 'admin' && $arg[1] === 'structure' && $arg['2'] === 'block' && (empty($arg[3]) || $arg[3] === 'list')) { $output = '' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the Save blocks button at the bottom of the page. Click the configure link next to each block to configure its specific title and visibility settings.') . '
'; - $output .= '' . l(t('Demonstrate block regions (@theme)', array('@theme' => $themes[$demo_theme]->info['name'])), 'admin/structure/block/demo/' . $demo_theme) . '
'; + if (!empty($arg[4])) { + if (drupal_theme_access($arg[4])) { + $demo_theme = $arg[4]; + } + } + else { + $demo_theme = variable_get('theme_default', 'stark'); + } + if (isset($demo_theme)) { + $themes = list_themes(); + $output .= '' . l(t('Demonstrate block regions (@theme)', array('@theme' => $themes[$demo_theme]->info['name'])), 'admin/structure/block/demo/' . $demo_theme) . '
'; + } return $output; } } @@ -103,12 +113,10 @@ function block_permission() { * Implements hook_menu(). */ function block_menu() { - $default_theme = variable_get('theme_default', 'stark'); $items['admin/structure/block'] = array( 'title' => 'Blocks', 'description' => 'Configure what block content appears in your site\'s sidebars and other regions.', 'page callback' => 'block_admin_display', - 'page arguments' => array($default_theme), 'access arguments' => array('administer blocks'), 'file' => 'block.admin.inc', ); @@ -141,39 +149,94 @@ function block_menu() { 'type' => MENU_LOCAL_ACTION, 'file' => 'block.admin.inc', ); - foreach (list_themes() as $key => $theme) { - $items['admin/structure/block/list/' . $key] = array( - 'title' => check_plain($theme->info['name']), - 'page arguments' => array($key), - 'type' => $key == $default_theme ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, - 'weight' => $key == $default_theme ? -10 : 0, - 'access callback' => '_block_themes_access', - 'access arguments' => array($key), - 'file' => 'block.admin.inc', - ); - if ($key != $default_theme) { - $items['admin/structure/block/list/' . $key . '/add'] = array( - 'title' => 'Add block', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('block_add_block_form'), - 'access arguments' => array('administer blocks'), - 'type' => MENU_LOCAL_ACTION, - 'file' => 'block.admin.inc', + + $items['admin/structure/block/list/default'] = array( + 'title' => 'Default theme blocks', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/structure/block/list/%theme'] = array( + 'title' => 'Theme blocks', + 'title callback' => '_theme_menu_title', + 'title arguments' => array(4), + 'page callback' => 'block_admin_display', + 'page arguments' => array(4), + 'type' => MENU_LOCAL_TASK, + 'access callback' => '_block_themes_access', + 'access arguments' => array(4), + 'file' => 'block.admin.inc', + ); + $items['admin/structure/block/list/%theme/add'] = array( + 'title' => 'Add block', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('block_add_block_form'), + 'access arguments' => array('administer blocks'), + 'type' => MENU_LOCAL_ACTION, + 'file' => 'block.admin.inc', + ); + $items['admin/structure/block/demo/%theme'] = array( + 'title' => 'Theme block region demo', + 'title callback' => '_theme_menu_title', + 'title arguments' => array(4), + 'page callback' => 'block_admin_demo', + 'page arguments' => array(4), + 'type' => MENU_CALLBACK, + 'access callback' => '_block_themes_access', + 'access arguments' => array(4), + 'theme callback' => '_block_custom_theme', + 'theme arguments' => array(4), + 'file' => 'block.admin.inc', + ); + return $items; +} + +/** + * Implements hook_menu_local_tasks_alter(). + */ +function block_menu_local_tasks_alter(&$data, $router_item, $root_path) { + if ($root_path == 'admin/structure/block' || strpos($root_path, 'admin/structure/block/list') === 0) { + // Replace the tab title of the default local task with the name of the + // default theme. + $themes = list_themes(); + $default_theme = variable_get('theme_default', 'stark'); + foreach ($data['tabs'][0] as $i => &$tab) { + if (isset($tab['#link']['path'])) { + if ($tab['#link']['path'] == 'admin/structure/block/list/default') { + $tab['#link']['title'] = $themes[$default_theme]->info['name']; + } + elseif ($tab['#link']['path'] == 'admin/structure/block/list/%') { + $selected_theme_index = $i; + } + } + } + + // Expand the dynamic %theme argument into a tab for each theme. + $selected_theme = isset($router_item['original_map'][4]) ? $router_item['original_map'][4] : NULL; + foreach ($themes as $theme) { + // The default theme is always exposed as default local task already. + if ($theme->name === $default_theme) { + continue; + } + // If the current page shows the interface for a selected theme, then the + // menu router item with the %theme argument will expose the selected + // theme already; do not duplicate that tab, but ensure it appears in a + // consistent order when switching between tabs. + if ($theme->name === $selected_theme) { + $data['tabs'][0][] = $data['tabs'][0][$selected_theme_index]; + unset($data['tabs'][0][$selected_theme_index]); + continue; + } + $data['tabs'][0][] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => $theme->info['name'], + 'href' => 'admin/structure/block/list/' . $theme->name, + ), + '#active' => $theme->name === $selected_theme, + '#weight' => $theme->name === $default_theme ? -10 : 0, ); } - $items['admin/structure/block/demo/' . $key] = array( - 'title' => check_plain($theme->info['name']), - 'page callback' => 'block_admin_demo', - 'page arguments' => array($key), - 'type' => MENU_CALLBACK, - 'access callback' => '_block_themes_access', - 'access arguments' => array($key), - 'theme callback' => '_block_custom_theme', - 'theme arguments' => array($key), - 'file' => 'block.admin.inc', - ); } - return $items; } /** @@ -208,7 +271,9 @@ function _block_themes_access($theme) { function _block_custom_theme($theme = NULL) { // We return exactly what was passed in, to guarantee that the page will // always be displayed using the theme whose blocks are being configured. - return $theme; + if (isset($theme->name)) { + return $theme->name; + } } /** @@ -273,7 +338,7 @@ function block_page_build(&$page) { $all_regions = system_region_list($theme); $item = menu_get_item(); - if ($item['path'] != 'admin/structure/block/demo/' . $theme) { + if ($item['path'] != 'admin/structure/block/demo/%') { // Load all region content assigned via blocks. foreach (array_keys($all_regions) as $region) { // Assign blocks to region. @@ -293,8 +358,7 @@ function block_page_build(&$page) { } else { // Append region description if we are rendering the regions demo page. - $item = menu_get_item(); - if ($item['path'] == 'admin/structure/block/demo/' . $theme) { + if ($item['path'] == 'admin/structure/block/demo/%') { $visible_regions = array_keys(system_region_list($theme, REGIONS_VISIBLE)); foreach ($visible_regions as $region) { $description = '