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 = '