diff --git a/core/includes/module.inc b/core/includes/module.inc index 7a1c076..724fb46 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -35,7 +35,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); } @@ -58,7 +58,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 @@ -74,7 +74,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 @@ -121,13 +121,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(), ); @@ -186,7 +186,7 @@ function system_list($type) { $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', @@ -197,30 +197,26 @@ function system_list($type) { // Build a list of themes. $enabled_themes = (array) 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 + 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. @@ -267,17 +263,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 8a35b6e..3fd144b 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); } } @@ -689,14 +722,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. @@ -1460,29 +1493,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; } /** @@ -1503,17 +1641,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..7513cb8 100644 --- a/core/includes/theme.maintenance.inc +++ b/core/includes/theme.maintenance.inc @@ -17,11 +17,7 @@ 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 . '/core/includes/database.inc'; 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'; @@ -35,13 +31,6 @@ function _drupal_maintenance_theme() { $custom_theme = (isset($conf['maintenance_theme']) ? $conf['maintenance_theme'] : 'seven'); } else { - // The bootstrap was not complete. So we are operating in a crippled - // environment, we need to bootstrap just enough to allow hook invocations - // to work. See _drupal_log_error(). - if (!class_exists('Drupal\Core\Database\Database', FALSE)) { - require_once DRUPAL_ROOT . '/core/includes/database.inc'; - } - // We use the default theme as the maintenance theme. If a default theme // isn't specified in the database or in settings.php, we use Bartik. // @todo Should use the actual default theme configured, but that depends on @@ -61,6 +50,9 @@ function _drupal_maintenance_theme() { } $themes = list_themes(); + if (empty($themes) || !isset($themes[$custom_theme])) { + $themes = _system_rebuild_theme_data(); + } // list_themes() triggers a drupal_alter() in maintenance mode, but we can't // let themes alter the .info data until we know a theme's base themes. So diff --git a/core/includes/update.inc b/core/includes/update.inc index 299a81c..655b23f 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -276,7 +276,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/ExceptionController.php b/core/lib/Drupal/Core/ExceptionController.php index 92d4454..a2d82b1 100644 --- a/core/lib/Drupal/Core/ExceptionController.php +++ b/core/lib/Drupal/Core/ExceptionController.php @@ -267,9 +267,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 has encountered an 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 ffa0b0b..e51416b 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 26bcb25..0216f27 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,96 @@ 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' || $root_path === 'admin/structure/block/list/%') { + $tabs = &$data['tabs'][0]['output']; + // Determine the currently selected tab, if any. + $selected_index = -1; + $selected_id = ''; + foreach ($tabs as $index => &$tab) { + if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'admin/structure/block/list/%') { + $selected_index = $index; + $selected_id = $router_item['original_map'][4]; + break; + } + } + // Expand the dynamic %theme argument into a tab for each theme. + $themes = list_themes(); + $default_theme = variable_get('theme_default', 'stark'); + foreach ($themes as $theme) { + // The default theme is always exposed as default local task already. + if ($theme->name === $default_theme) { + $tabs[0]['#link']['title'] = $theme->info['name']; + continue; + } + // If the current page is the active tab registered in hook_menu(), then + // the menu router item with the dynamic argument will be exposed already. + // We must not duplicate that tab, but in order to ensure that all of our + // tabs appear in a consistent order when switching between tabs, we need + // to re-inject it. + if ($theme->name === $selected_id) { + $tabs[$selected_index]['#link']['title'] = $theme->info['name']; + $tabs[] = $tabs[$selected_index]; + unset($tabs[$selected_index]); + continue; + } + $tabs[] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => $theme->info['name'], + 'href' => 'admin/structure/block/list/' . $theme->name, + 'localized_options' => array('html' => FALSE), + ), ); } - $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', - ); + if (count($themes) > 1) { + $data['tabs'][0]['count']++; + } } - return $items; } /** @@ -208,7 +273,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 +340,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 +360,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 = '
' . $all_regions[$region] . '
'; @@ -412,6 +478,10 @@ function _block_rehash($theme = NULL) { } $regions = system_region_list($theme); + if (is_object($theme)) { + $theme = $theme->name; + } + // These are the blocks the function will return. $blocks = array(); // These are the blocks defined by code and modified by the database. diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php index c3a220f..ddee5dc 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php @@ -37,9 +37,9 @@ function testAdminTheme() { $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes')); $this->drupalLogin($admin_user); - // Ensure that access to block admin page is denied when theme is disabled. + // Ensure the block admin page cannot be accessed for a disabled theme. $this->drupalGet('admin/structure/block/list/bartik'); - $this->assertResponse(403); + $this->assertResponse(404); // Enable admin theme and confirm that tab is accessible. theme_enable(array('bartik')); diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php index af5acb9..2bf01ed 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php @@ -65,7 +65,7 @@ function testBlockNotInHiddenRegion() { $theme = 'block_test_theme'; theme_enable(array($theme)); variable_set('theme_default', $theme); - menu_router_rebuild(); + menu_cache_clear_all(); // Ensure that "block_test_theme" is set as the default theme. $this->drupalGet('admin/structure/block'); diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php index ccf9834..a7bcd7c 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php @@ -122,6 +122,7 @@ function testShortcutLinkDelete() { */ function testNoShortcutLink() { // Change to a theme that displays shortcuts. + theme_enable(array('seven')); variable_set('theme_default', 'seven'); $this->drupalGet('page-that-does-not-exist'); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 93cdbf2..ec66409 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -883,11 +883,6 @@ protected function prepareEnvironment() { unset($GLOBALS['theme_key']); unset($GLOBALS['theme']); - // Re-initialize the theme to ensure that tests do not see an inconsistent - // behavior when calling functions that would initialize the theme if it has - // not been initialized yet. - drupal_theme_initialize(); - // Log fatal errors. ini_set('log_errors', 1); ini_set('error_log', $this->public_files_directory . '/error.log'); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php index 648aa42..0b112ea 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php @@ -70,6 +70,11 @@ protected function setUp() { return FALSE; } + // Re-initialize the theme to ensure that tests do not see an inconsistent + // behavior when calling functions that would initialize the theme if it has + // not been initialized yet. + drupal_theme_initialize(); + // Set user agent to be consistent with WebTestBase. $_SERVER['HTTP_USER_AGENT'] = $this->databasePrefix; diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 001e521..e5f43ab 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -760,6 +760,11 @@ protected function setUp() { $this->rebuildContainer(); } + // Re-initialize the theme to ensure that tests do not see an inconsistent + // behavior when calling functions that would initialize the theme if it has + // not been initialized yet. + drupal_theme_initialize(); + // Reset/rebuild all data structures after enabling the modules. $this->resetAll(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php index db1d671..4788705 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Batch/PageTest.php @@ -35,8 +35,8 @@ public static function getInfo() { function testBatchProgressPageTheme() { // Make sure that the page which starts the batch (an administrative page) // is using a different theme than would normally be used by the batch API. + theme_enable(array('bartik', 'seven')); variable_set('theme_default', 'bartik'); - theme_enable(array('seven')); 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')); diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php index 111e6b7..d0d0896 100644 --- a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php @@ -206,7 +206,7 @@ function testAdministrationTheme() { $this->assertRaw('core/themes/stark', 'Site default theme used on the add content page.'); // Reset to the default theme settings. - variable_set('theme_default', 'bartik'); + variable_set('theme_default', 'stark'); $edit = array( 'admin_theme' => '0', 'node_admin_theme' => FALSE, @@ -214,10 +214,10 @@ function testAdministrationTheme() { $this->drupalPost('admin/appearance', $edit, t('Save configuration')); $this->drupalGet('admin'); - $this->assertRaw('core/themes/bartik', 'Site default theme used on administration page.'); + $this->assertRaw('core/themes/stark', 'Site default theme used on administration page.'); $this->drupalGet('node/add'); - $this->assertRaw('core/themes/bartik', 'Site default theme used on the add content page.'); + $this->assertRaw('core/themes/stark', 'Site default theme used on the add content page.'); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php index f1308c5..3e214c6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php @@ -141,18 +141,20 @@ function testTemplateOverride() { * Test the list_themes() function. */ function testListThemes() { + theme_enable(array('test_subtheme')); $themes = list_themes(); // Check if drupal_theme_access() retrieves enabled themes properly from list_themes(). $this->assertTrue(drupal_theme_access('test_theme'), 'Enabled theme detected'); - // Check if list_themes() returns disabled themes. - $this->assertTrue(array_key_exists('test_basetheme', $themes), 'Disabled theme detected'); + // Check for base theme and subtheme lists. $base_theme_list = array('test_basetheme' => 'Theme test base theme'); $sub_theme_list = array('test_subtheme' => 'Theme test subtheme'); $this->assertIdentical($themes['test_basetheme']->sub_themes, $sub_theme_list, 'Base theme\'s object includes list of subthemes.'); $this->assertIdentical($themes['test_subtheme']->base_themes, $base_theme_list, 'Subtheme\'s object includes list of base themes.'); + // Check for theme engine in subtheme. $this->assertIdentical($themes['test_subtheme']->engine, 'phptemplate', 'Subtheme\'s object includes the theme engine.'); + // Check for theme engine prefix. $this->assertIdentical($themes['test_basetheme']->prefix, 'phptemplate', 'Base theme\'s object includes the theme engine prefix.'); $this->assertIdentical($themes['test_subtheme']->prefix, 'phptemplate', 'Subtheme\'s object includes the theme engine prefix.'); @@ -162,6 +164,7 @@ function testListThemes() { * Test the theme_get_setting() function. */ function testThemeGetSetting() { + theme_enable(array('test_subtheme')); $GLOBALS['theme_key'] = 'test_theme'; $this->assertIdentical(theme_get_setting('theme_test_setting'), 'default value', 'theme_get_setting() uses the default theme automatically.'); $this->assertNotEqual(theme_get_setting('subtheme_override', 'test_basetheme'), theme_get_setting('subtheme_override', 'test_subtheme'), 'Base theme\'s default settings values can be overridden by subtheme.'); diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 46b4b9d..9570b5a 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -119,8 +119,9 @@ function system_themes_page() { uasort($themes, 'system_sort_modules_by_info_name'); $theme_default = variable_get('theme_default', 'stark'); - $theme_groups = array(); + $theme_groups = array('enabled' => array(), 'disabled' => array()); $admin_theme = variable_get('admin_theme', 0); + $admin_theme_options = array(); foreach ($themes as &$theme) { if (!empty($theme->info['hidden'])) { @@ -281,12 +282,10 @@ function system_themes_admin_form_submit($form, &$form_state) { function system_theme_enable() { if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) { $theme = $_REQUEST['theme']; - // Get current list of themes. - $themes = list_themes(); - // Check if the specified theme is one recognized by the system. - if (!empty($themes[$theme])) { + if (drupal_get_path('theme', $theme)) { theme_enable(array($theme)); + $themes = list_themes(); drupal_set_message(t('The %theme theme has been enabled.', array('%theme' => $themes[$theme]->info['name']))); } else { @@ -331,26 +330,17 @@ function system_theme_disable() { function system_theme_default() { if (isset($_REQUEST['theme']) && isset($_REQUEST['token']) && drupal_valid_token($_REQUEST['token'], 'system-theme-operation-link')) { $theme = $_REQUEST['theme']; - // Get current list of themes. - $themes = list_themes(); - // Check if the specified theme is one recognized by the system. - if (!empty($themes[$theme])) { + if (drupal_get_path('theme', $theme)) { + $themes = list_themes(); // Enable the theme if it is currently disabled. - if (empty($themes[$theme]->status)) { - theme_enable(array($theme)); + if (!isset($themes[$theme])) { + theme_enable(array($theme)); + $themes = list_themes(); } // Set the default theme. variable_set('theme_default', $theme); - // Rebuild the menu. This duplicates the menu_router_rebuild() in - // theme_enable(). However, modules must know the current default theme in - // order to use this information in hook_menu() or hook_menu_alter() - // implementations, and doing the variable_set() before the theme_enable() - // could result in a race condition where the theme is default but not - // enabled. - menu_router_rebuild(); - // The status message depends on whether an admin theme is currently in use: // a value of 0 means the admin theme is set to be the default theme. $admin_theme = variable_get('admin_theme', 0); @@ -375,14 +365,17 @@ function system_theme_default() { /** * Form builder; display theme configuration for entire site and individual themes. * - * @param $key - * A theme name. + * @param $theme + * (optional) A theme object. + * * @return * The form structure. + * * @ingroup forms * @see system_theme_settings_submit() */ -function system_theme_settings($form, &$form_state, $key = '') { +function system_theme_settings($form, &$form_state, $theme = NULL) { + $key = !empty($theme) ? $theme->name : ''; // Default settings are defined in theme_get_setting() in includes/theme.inc if ($key) { $var = 'theme_' . $key . '_settings'; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 1492cf0..1a92ef1 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -686,19 +686,18 @@ function system_menu() { $items['admin/appearance/settings/global'] = array( 'title' => 'Global settings', 'type' => MENU_DEFAULT_LOCAL_TASK, - 'weight' => -1, + 'weight' => -10, + ); + $items['admin/appearance/settings/%theme'] = array( + 'title' => 'Theme settings', + 'title callback' => '_theme_menu_title', + 'title arguments' => array(3), + 'page arguments' => array('system_theme_settings', 3), + 'access callback' => '_system_themes_access', + 'access arguments' => array(3), + 'type' => MENU_LOCAL_TASK, + 'file' => 'system.admin.inc', ); - - foreach (list_themes() as $key => $theme) { - $items['admin/appearance/settings/' . $theme->name] = array( - 'title' => $theme->info['name'], - 'page arguments' => array('system_theme_settings', $theme->name), - 'type' => MENU_LOCAL_TASK, - 'access callback' => '_system_themes_access', - 'access arguments' => array($key), - 'file' => 'system.admin.inc', - ); - } // Modules. $items['admin/modules'] = array( @@ -1033,6 +1032,51 @@ function system_menu() { } /** + * Implements hook_menu_local_tasks_alter(). + */ +function system_menu_local_tasks_alter(&$data, $router_item, $root_path) { + if ($root_path === 'admin/appearance/settings' || $root_path === 'admin/appearance/settings/%') { + $tabs = &$data['tabs'][1]['output']; + // Determine the currently selected tab, if any. + $selected_index = -1; + $selected_id = ''; + foreach ($tabs as $index => &$tab) { + if (isset($tab['#link']['path']) && $tab['#link']['path'] == 'admin/appearance/settings/%') { + $selected_index = $index; + $selected_id = $router_item['original_map'][3]; + break; + } + } + // Expand the dynamic %theme argument into a tab for each theme. + $themes = list_themes(); + foreach ($themes as $theme) { + // If the current page is the active tab registered in hook_menu(), then + // the menu router item with the dynamic argument will be exposed already. + // We must not duplicate that tab, but in order to ensure that all of our + // tabs appear in a consistent order when switching between tabs, we need + // to re-inject it. + if ($theme->name === $selected_id) { + $tabs[$selected_index]['#link']['title'] = $theme->info['name']; + $tabs[] = $tabs[$selected_index]; + unset($tabs[$selected_index]); + continue; + } + $tabs[] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => $theme->info['name'], + 'href' => 'admin/appearance/settings/' . $theme->name, + 'localized_options' => array('html' => FALSE), + ), + ); + } + if (count($themes) > 1) { + $data['tabs'][1]['count']++; + } + } +} + +/** * Theme callback for the default batch page. */ function _system_batch_theme() { @@ -2698,6 +2742,7 @@ function system_get_info($type, $name = NULL) { } } else { + // @todo Migrate theme info to ModuleInfo implementation. $list = system_list($type); foreach ($list as $shortname => $item) { if (!empty($item->status)) { @@ -2723,6 +2768,8 @@ function system_get_info($type, $name = NULL) { * returned. * * @see Drupal\Core\Utility\ModuleInfo + * + * @todo Merge this helper entirely into system_get_info()? */ function system_get_module_info($property) { static $info; @@ -2935,6 +2982,9 @@ function _system_rebuild_theme_data() { if (!empty($themes[$key]->info['base theme'])) { $sub_themes[] = $key; + // Add the defined base theme as dependency to the theme, in order to + // re-use the dependency graph resolution of _module_build_dependencies(). + $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme']; } if ($themes[$key]->info['engine'] == 'theme') { $filename = dirname($themes[$key]->uri) . '/' . $themes[$key]->name . '.theme'; @@ -2999,30 +3049,36 @@ function _system_rebuild_theme_data() { * Array of all available themes and their data. */ function system_rebuild_theme_data() { - $themes = _system_rebuild_theme_data(); - ksort($themes); - // @todo This function has no business in determining/setting the status of - // a theme, but various other functions expect it to return themes with a - // $status property. system_list() stores the return value of this function - // in state, and ensures to set/override the $status property for each theme - // based on the current config. Remove this code when themes have a proper - // installation status. - // @see http://drupal.org/node/1067408 - $enabled_themes = (array) config('system.theme')->get('enabled'); - $files = array(); - foreach ($themes as $name => $theme) { - $theme->status = (int) isset($enabled_themes[$name]); - $files[$name] = $theme->filename; - } - // Replace last known theme data state. - // @todo Obsolete with proper installation status for themes. - state()->set('system.theme.data', $themes); - - // Store filenames to allow system_list() and drupal_get_filename() to - // retrieve them without having to rebuild or scan the filesystem. - state()->set('system.theme.files', $files); + $cache = &drupal_static(__FUNCTION__); + // Only rebuild once per request. $themes and $cache cannot be combined into + // one variable, because $cache is reset by reference from system_list_reset() + // during a rebuild. + if (!isset($cache)) { + $themes = _system_rebuild_theme_data(); + $files = array(); + ksort($themes); + // Add name, status, and schema version. + $enabled_themes = (array) config('system.theme')->get('enabled'); + $disabled_themes = (array) config('system.theme.disabled')->get(); + $all_themes = $enabled_themes + $disabled_themes; + foreach ($themes as $name => $theme) { + $theme->name = $name; + $theme->weight = isset($all_themes[$name]) ? $all_themes[$name] : 0; + $theme->status = (int) isset($enabled_themes[$name]); + $theme->schema_version = SCHEMA_UNINSTALLED; + $files[$name] = $theme->filename; + + // Replace last known theme data state. + state()->set('system.theme.data.' . $name, $theme); + } + $themes = _module_build_dependencies($themes); + $cache = $themes; - return $themes; + // Store filenames to allow system_list() and drupal_get_filename() to + // retrieve them without having to rebuild or scan the filesystem. + state()->set('system.theme.files', $files); + } + return $cache; } /** @@ -3080,25 +3136,26 @@ function _system_default_theme_features() { /** * Get a list of available regions from a specified theme. * - * @param $theme_key - * The name of a theme. + * @param $theme + * A theme object, or the name of a theme. * @param $show * Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden * regions. * @return * An array of regions in the form $region['name'] = 'description'. */ -function system_region_list($theme_key, $show = REGIONS_ALL) { - $themes = list_themes(); - if (!isset($themes[$theme_key])) { - return array(); +function system_region_list($theme, $show = REGIONS_ALL) { + if (!is_object($theme)) { + $themes = list_themes(); + if (!isset($themes[$theme])) { + return array(); + } + $theme = $themes[$theme]; } - $list = array(); - $info = $themes[$theme_key]->info; // If requested, suppress hidden regions. See block_admin_display_form(). - foreach ($info['regions'] as $name => $label) { - if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) { + foreach ($theme->info['regions'] as $name => $label) { + if ($show == REGIONS_ALL || !isset($theme->info['regions_hidden']) || !in_array($name, $theme->info['regions_hidden'])) { $list[$name] = t($label); } } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/ThemeTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/ThemeTest.php index b226970..6ab0e74 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/ThemeTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/ThemeTest.php @@ -25,8 +25,8 @@ function setUp() { // Make sure we are using distinct default and administrative themes for // the duration of these tests. + theme_enable(array('bartik', 'seven')); variable_set('theme_default', 'bartik'); - theme_enable(array('seven')); variable_set('admin_theme', 'seven'); // Create and log in as a user who has permission to add and edit taxonomy