Issue #2109287 by dawehner, Cottser, tim.plunkett, kim.pepper: Replace list_themes() with a service.

8.0.x
Nathaniel Catchpole 2013-12-05 10:06:32 +00:00
parent 1ceeda436a
commit 67e93b23ac
8 changed files with 1072 additions and 268 deletions

View File

@ -173,6 +173,9 @@ services:
default_plugin_manager:
abstract: true
arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
theme_handler:
class: Drupal\Core\Extension\ThemeHandler
arguments: ['@config.factory', '@module_handler', '@cache.cache', '@info_parser', '@router.builder']
entity.manager:
class: Drupal\Core\Entity\EntityManager
arguments: ['@container.namespaces', '@service_container', '@module_handler', '@cache.cache', '@language_manager', '@string_translation']

View File

@ -484,7 +484,14 @@ function install_begin_request(&$install_state) {
// Register the info parser.
$container->register('info_parser', 'Drupal\Core\Extension\InfoParser');
$container->register('theme_handler', 'Drupal\Core\Extension\ThemeHandler')
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('module_handler'))
->addArgument(new Reference('cache.cache'))
->addArgument(new Reference('info_parser'));
}
// Set the request in the kernel to the new created Request above
// so it is available to the rest of the installation process.
$container->set('request', $request);

View File

@ -355,52 +355,19 @@ function drupal_theme_rebuild() {
* the themes' machine names, and the values are the themes' human-readable
* names. This element is not set if there are no themes on the system that
* declare this theme as their base theme.
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('theme_handler')->listInfo().
*/
function list_themes($refresh = FALSE) {
$list = &drupal_static(__FUNCTION__, array());
/** @var \Drupal\Core\Extension\ThemeHandler $theme_handler */
$theme_handler = \Drupal::service('theme_handler');
if ($refresh) {
$list = array();
$theme_handler->reset();
system_list_reset();
}
if (empty($list)) {
$list = array();
// Extract from the database only when it is available.
// Also check that the site is not in the middle of an install or update.
try {
$themes = system_list('theme');
}
catch (Exception $e) {
// If the database is not available, rebuild the theme data.
$themes = _system_rebuild_theme_data();
}
foreach ($themes as $theme) {
foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet => $path) {
$theme->stylesheets[$media][$stylesheet] = $path;
}
}
foreach ($theme->info['scripts'] as $script => $path) {
$theme->scripts[$script] = $path;
}
if (isset($theme->info['engine'])) {
$theme->engine = $theme->info['engine'];
}
if (isset($theme->info['base theme'])) {
$theme->base_theme = $theme->info['base theme'];
}
// Status is normally retrieved from the database. Add zero values when
// read from the installation directory to prevent notices.
if (!isset($theme->status)) {
$theme->status = 0;
}
$list[$theme->name] = $theme;
}
}
return $list;
return $theme_handler->listInfo();
}
/**
@ -413,38 +380,15 @@ function list_themes($refresh = FALSE) {
* An array of available themes.
* @param $key
* The name of the theme whose base we are looking for.
* @param $used_keys
* (optional) A recursion parameter preventing endless loops. Defaults to
* NULL.
*
* @return
* Returns an array of all of the theme's ancestors; the first element's value
* will be NULL if an error occurred.
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('theme_handler')->getBaseThemes().
*/
function drupal_find_base_themes($themes, $key, $used_keys = array()) {
$base_key = $themes[$key]->info['base theme'];
// Does the base theme exist?
if (!isset($themes[$base_key])) {
return array($base_key => NULL);
}
$current_base_theme = array($base_key => $themes[$base_key]->info['name']);
// Is the base theme itself a child of another theme?
if (isset($themes[$base_key]->info['base theme'])) {
// Do we already know the base themes of this theme?
if (isset($themes[$base_key]->base_themes)) {
return $themes[$base_key]->base_themes + $current_base_theme;
}
// Prevent loops.
if (!empty($used_keys[$base_key])) {
return array($base_key => NULL);
}
$used_keys[$base_key] = TRUE;
return drupal_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme;
}
// If we get here, then this is our parent theme.
return $current_base_theme;
function drupal_find_base_themes($themes, $key) {
return \Drupal::service('theme_handler')->getBaseThemes($themes, $key);
}
/**
@ -1133,38 +1077,11 @@ function theme_settings_convert_to_config(array $theme_settings, Config $config)
*
* @param $theme_list
* An array of theme names.
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('theme_handler')->enable().
*/
function theme_enable($theme_list) {
drupal_clear_css_cache();
$theme_config = \Drupal::config('system.theme');
$disabled_themes = \Drupal::config('system.theme.disabled');
foreach ($theme_list as $key) {
// Throw an exception if the theme name is too long.
if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
throw new ExtensionNameLengthException(format_string('Theme name %name is over the maximum allowed length of @max characters.', array(
'%name' => $key,
'@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
)));
}
// The value is not used; the weight is ignored for themes currently.
$theme_config->set("enabled.$key", 0)->save();
$disabled_themes->clear($key)->save();
// Refresh the theme list as config_install_default_config() needs an
// updated list to work.
list_themes(TRUE);
// Install default configuration of the theme.
config_install_default_config('theme', $key);
}
\Drupal::service('router.builder')->rebuild();
menu_router_rebuild();
\Drupal::cache('cache')->deleteTags(array('local_task' => 1));
drupal_theme_rebuild();
// Invoke hook_themes_enabled() after the themes have been enabled.
\Drupal::moduleHandler()->invokeAll('themes_enabled', array($theme_list));
\Drupal::service('theme_handler')->enable($theme_list);
}
/**
@ -1172,36 +1089,11 @@ function theme_enable($theme_list) {
*
* @param $theme_list
* An array of theme names.
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('theme_handler')->disable().
*/
function theme_disable($theme_list) {
// Don't disable the default theme.
if ($pos = array_search(\Drupal::config('system.theme')->get('default'), $theme_list) !== FALSE) {
unset($theme_list[$pos]);
if (empty($theme_list)) {
return;
}
}
drupal_clear_css_cache();
$theme_config = \Drupal::config('system.theme');
$disabled_themes = \Drupal::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);
}
$theme_config->save();
$disabled_themes->save();
list_themes(TRUE);
\Drupal::service('router.builder')->rebuild();
menu_router_rebuild();
\Drupal::cache('cache')->deleteTags(array('local_task' => 1));
drupal_theme_rebuild();
// Invoke hook_themes_disabled after the themes have been disabled.
\Drupal::moduleHandler()->invokeAll('themes_disabled', array($theme_list));
\Drupal::service('theme_handler')->disable($theme_list);
}
/**

View File

@ -9,6 +9,7 @@ namespace Drupal\Core\DependencyInjection;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* ServiceProvider class for update.php service overrides.
@ -38,6 +39,12 @@ class UpdateServiceProvider implements ServiceProviderInterface {
->register("cache_factory", 'Drupal\Core\Cache\MemoryBackendFactory');
$container
->register('router.builder', 'Drupal\Core\Routing\RouteBuilderStatic');
$container->register('theme_handler', 'Drupal\Core\Extension\ThemeHandler')
->addArgument(new Reference('config.factory'))
->addArgument(new Reference('module_handler'))
->addArgument(new Reference('cache.cache'))
->addArgument(new Reference('info_parser'));
}
}

View File

@ -0,0 +1,500 @@
<?php
/**
* @file
* Contains \Drupal\Core\Extension\ThemeHandler.
*/
namespace Drupal\Core\Extension;
use Drupal\Component\Utility\String;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Routing\RouteBuilder;
use Drupal\Core\SystemListingInfo;
/**
* Default theme handler using the config system for enabled/disabled themes.
*/
class ThemeHandler implements ThemeHandlerInterface {
/**
* Contains the features enabled for themes by default.
*
* @var array
*/
protected $defaultFeatures = array(
'logo',
'favicon',
'name',
'slogan',
'node_user_picture',
'comment_user_picture',
'comment_user_verification',
'main_menu',
'secondary_menu',
);
/**
* A list of all currently available themes.
*
* @var array
*/
protected $list = array();
/**
* The config factory to get the enabled themes.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* The module handler to fire themes_enabled/themes_disabled hooks.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The cache backend to clear the local tasks cache.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBackend;
/**
* The info parser to parse the theme.info.yml files.
*
* @var \Drupal\Core\Extension\InfoParserInterface
*/
protected $infoParser;
/**
* The route builder to rebuild the routes if a theme is enabled.
*
* @var \Drupal\Core\Routing\RouteBuilder
*/
protected $routerBuilder;
/**
* The system listing info
*
* @var \Drupal\Core\SystemListingInfo
*/
protected $systemListingInfo;
/**
* Constructs a new ThemeHandler.
*
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The config factory to get the enabled themes.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to fire themes_enabled/themes_disabled hooks.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* The cache backend to clear the local tasks cache.
* @param \Drupal\Core\Extension\InfoParserInterface $info_parser
* The info parser to parse the theme.info.yml files.
* @param \Drupal\Core\Routing\RouteBuilder $route_builder
* (optional) The route builder to rebuild the routes if a theme is enabled.
* @param \Drupal\Core\SystemListingInfo $system_list_info
* (optional) The system listing info.
*/
public function __construct(ConfigFactory $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, RouteBuilder $route_builder = NULL, SystemListingInfo $system_list_info = NULL) {
$this->configFactory = $config_factory;
$this->moduleHandler = $module_handler;
$this->cacheBackend = $cache_backend;
$this->infoParser = $info_parser;
$this->routeBuilder = $route_builder;
$this->systemListingInfo = $system_list_info;
}
/**
* {@inheritdoc}
*/
public function enable(array $theme_list) {
$this->clearCssCache();
$theme_config = $this->configFactory->get('system.theme');
$disabled_themes = $this->configFactory->get('system.theme.disabled');
foreach ($theme_list as $key) {
// Throw an exception if the theme name is too long.
if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
throw new ExtensionNameLengthException(String::format('Theme name %name is over the maximum allowed length of @max characters.', array(
'%name' => $key,
'@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
)));
}
// The value is not used; the weight is ignored for themes currently.
$theme_config->set("enabled.$key", 0)->save();
$disabled_themes->clear($key)->save();
// Refresh the theme list as config_install_default_config() needs an
// updated list to work.
$this->reset();
// Install default configuration of the theme.
$this->configInstallDefaultConfig($key);
}
$this->resetSystem();
// Invoke hook_themes_enabled() after the themes have been enabled.
$this->moduleHandler->invokeAll('themes_enabled', array($theme_list));
}
/**
* {@inheritdoc}
*/
public function disable(array $theme_list) {
// Don't disable the default theme.
if ($pos = array_search($this->configFactory->get('system.theme')->get('default'), $theme_list) !== FALSE) {
unset($theme_list[$pos]);
if (empty($theme_list)) {
return;
}
}
$this->clearCssCache();
$theme_config = $this->configFactory->get('system.theme');
$disabled_themes = $this->configFactory->get('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);
}
$theme_config->save();
$disabled_themes->save();
$this->reset();
$this->resetSystem();
// Invoke hook_themes_disabled after the themes have been disabled.
$this->moduleHandler->invokeAll('themes_disabled', array($theme_list));
}
/**
* {@inheritdoc}
*/
public function listInfo() {
if (empty($this->list)) {
$this->list = array();
try {
$themes = $this->systemThemeList();
}
catch (\Exception $e) {
// If the database is not available, rebuild the theme data.
$themes = $this->rebuildThemeData();
}
foreach ($themes as $theme) {
foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet => $path) {
$theme->stylesheets[$media][$stylesheet] = $path;
}
}
foreach ($theme->info['scripts'] as $script => $path) {
$theme->scripts[$script] = $path;
}
if (isset($theme->info['engine'])) {
$theme->engine = $theme->info['engine'];
}
if (isset($theme->info['base theme'])) {
$theme->base_theme = $theme->info['base theme'];
}
// Status is normally retrieved from the database. Add zero values when
// read from the installation directory to prevent notices.
if (!isset($theme->status)) {
$theme->status = 0;
}
$this->list[$theme->name] = $theme;
}
}
return $this->list;
}
/**
* {@inheritdoc}
*/
public function reset() {
// listInfo() calls system_info() which has a lot of side effects that have
// to be triggered like the classloading of theme classes.
$this->list = array();
$this->systemListReset();
$this->listInfo();
$this->list = array();
}
/**
* {@inheritdoc}
*/
public function rebuildThemeData() {
// Find themes.
$listing = $this->getSystemListingInfo();
$themes = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes', 'name', 1);
// Allow modules to add further themes.
if ($module_themes = $this->moduleHandler->invokeAll('system_theme_info')) {
foreach ($module_themes as $name => $uri) {
// @see file_scan_directory()
$themes[$name] = (object) array(
'uri' => $uri,
'filename' => pathinfo($uri, PATHINFO_FILENAME),
'name' => $name,
);
}
}
// Find theme engines.
$listing = $this->getSystemListingInfo();
$engines = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines', 'name', 1);
// Set defaults for theme info.
$defaults = array(
'engine' => 'twig',
'regions' => array(
'sidebar_first' => 'Left sidebar',
'sidebar_second' => 'Right sidebar',
'content' => 'Content',
'header' => 'Header',
'footer' => 'Footer',
'highlighted' => 'Highlighted',
'help' => 'Help',
'page_top' => 'Page top',
'page_bottom' => 'Page bottom',
),
'description' => '',
'features' => $this->defaultFeatures,
'screenshot' => 'screenshot.png',
'php' => DRUPAL_MINIMUM_PHP,
'stylesheets' => array(),
'scripts' => array(),
);
$sub_themes = array();
// Read info files for each theme.
foreach ($themes as $key => $theme) {
$themes[$key]->filename = $theme->uri;
$themes[$key]->info = $this->infoParser->parse($theme->uri) + $defaults;
// Skip this extension if its type is not theme.
if (!isset($themes[$key]->info['type']) || $themes[$key]->info['type'] != 'theme') {
unset($themes[$key]);
continue;
}
// Add the info file modification time, so it becomes available for
// contributed modules to use for ordering theme lists.
$themes[$key]->info['mtime'] = filemtime($theme->uri);
// Invoke hook_system_info_alter() to give installed modules a chance to
// modify the data in the .info.yml files if necessary.
$type = 'theme';
$this->moduleHandler->alter('system_info', $themes[$key]->info, $themes[$key], $type);
if (!empty($themes[$key]->info['base theme'])) {
$sub_themes[] = $key;
}
$engine = $themes[$key]->info['engine'];
if (isset($engines[$engine])) {
$themes[$key]->owner = $engines[$engine]->uri;
$themes[$key]->prefix = $engines[$engine]->name;
$themes[$key]->template = TRUE;
}
// Prefix stylesheets and scripts with module path.
$path = dirname($theme->uri);
$theme->info['stylesheets'] = $this->themeInfoPrefixPath($theme->info['stylesheets'], $path);
$theme->info['scripts'] = $this->themeInfoPrefixPath($theme->info['scripts'], $path);
// Give the screenshot proper path information.
if (!empty($themes[$key]->info['screenshot'])) {
$themes[$key]->info['screenshot'] = $path . '/' . $themes[$key]->info['screenshot'];
}
}
// Now that we've established all our master themes, go back and fill in
// data for sub-themes.
foreach ($sub_themes as $key) {
$themes[$key]->base_themes = $this->doGetBaseThemes($themes, $key);
// Don't proceed if there was a problem with the root base theme.
if (!current($themes[$key]->base_themes)) {
continue;
}
$base_key = key($themes[$key]->base_themes);
foreach (array_keys($themes[$key]->base_themes) as $base_theme) {
$themes[$base_theme]->sub_themes[$key] = $themes[$key]->info['name'];
}
// Copy the 'owner' and 'engine' over if the top level theme uses a theme
// engine.
if (isset($themes[$base_key]->owner)) {
if (isset($themes[$base_key]->info['engine'])) {
$themes[$key]->info['engine'] = $themes[$base_key]->info['engine'];
$themes[$key]->owner = $themes[$base_key]->owner;
$themes[$key]->prefix = $themes[$base_key]->prefix;
}
else {
$themes[$key]->prefix = $key;
}
}
}
return $themes;
}
/**
* Prefixes all values in an .info.yml file array with a given path.
*
* This helper function is mainly used to prefix all array values of an
* .info.yml file property with a single given path (to the module or theme);
* e.g., to prefix all values of the 'stylesheets' or 'scripts' properties
* with the file path to the defining module/theme.
*
* @param array $info
* A nested array of data of an .info.yml file to be processed.
* @param string $path
* A file path to prepend to each value in $info.
*
* @return array
* The $info array with prefixed values.
*
* @see _system_rebuild_module_data()
* @see self::rebuildThemeData()
*/
protected function themeInfoPrefixPath(array $info, $path) {
foreach ($info as $key => $value) {
// Recurse into nested values until we reach the deepest level.
if (is_array($value)) {
$info[$key] = $this->themeInfoPrefixPath($info[$key], $path);
}
// Unset the original value's key and set the new value with prefix, using
// the original value as key, so original values can still be looked up.
else {
unset($info[$key]);
$info[$value] = $path . '/' . $value;
}
}
return $info;
}
/**
* {@inheritdoc}
*/
public function getBaseThemes(array $themes, $theme) {
return $this->doGetBaseThemes($themes, $theme);
}
/**
* Finds the base themes for the specific theme.
*
* @param array $themes
* An array of available themes.
* @param string $theme
* The name of the theme whose base we are looking for.
* @param array $used_themes
* (optional) A recursion parameter preventing endless loops. Defaults to
* an empty array.
*
* @return array
* An array of base themes.
*/
protected function doGetBaseThemes(array $themes, $theme, $used_themes = array()) {
if (!isset($themes[$theme]->info['base theme'])) {
return array();
}
$base_key = $themes[$theme]->info['base theme'];
// Does the base theme exist?
if (!isset($themes[$base_key])) {
return array($base_key => NULL);
}
$current_base_theme = array($base_key => $themes[$base_key]->info['name']);
// Is the base theme itself a child of another theme?
if (isset($themes[$base_key]->info['base theme'])) {
// Do we already know the base themes of this theme?
if (isset($themes[$base_key]->base_themes)) {
return $themes[$base_key]->base_themes + $current_base_theme;
}
// Prevent loops.
if (!empty($used_themes[$base_key])) {
return array($base_key => NULL);
}
$used_themes[$base_key] = TRUE;
return $this->getBaseThemes($themes, $base_key, $used_themes) + $current_base_theme;
}
// If we get here, then this is our parent theme.
return $current_base_theme;
}
/**
* Returns a system listing info object.
*
* @return \Drupal\Core\SystemListingInfo
* The system listing object.
*/
protected function getSystemListingInfo() {
if (!isset($this->systemListingInfo)) {
$this->systemListingInfo = new SystemListingInfo();
}
return $this->systemListingInfo;
}
/**
* Installs the default theme config.
*
* @param string $theme
* The theme to install config for.
*/
protected function configInstallDefaultConfig($theme) {
config_install_default_config('theme', $theme);
}
/**
* Resets some other systems like rebuilding the route information or caches.
*/
protected function resetSystem() {
if ($this->routeBuilder) {
$this->routeBuilder->rebuild();
}
$this->systemListReset();
// @todo It feels wrong to have the requirement to clear the local tasks
// cache here.
$this->cacheBackend->deleteTags(array('local_task' => 1));
$this->themeRegistryRebuild();
}
/**
* Wraps system_list_reset().
*/
protected function systemListReset() {
system_list_reset();
}
/**
* Wraps drupal_clear_css_cache().
*/
protected function clearCssCache() {
drupal_clear_css_cache();
}
/**
* Wraps drupal_theme_rebuild().
*/
protected function themeRegistryRebuild() {
drupal_theme_rebuild();
}
/**
* Wraps system_list().
*
* @return array
* A list of themes keyed by name.
*/
protected function systemThemeList() {
return system_list('theme');
}
}

View File

@ -0,0 +1,108 @@
<?php
/**
* @file
* Contains \Drupal\Core\Extension\ThemeHandlerInterface.
*/
namespace Drupal\Core\Extension;
/**
* Manages the list of available themes as well as enable/disable them.
*/
interface ThemeHandlerInterface {
/**
* Enables a given list of themes.
*
* @param array $theme_list
* An array of theme names.
*
* @throws \Drupal\Core\Extension\ExtensionNameLengthException
* Thrown when the theme name is to long
*/
public function enable(array $theme_list);
/**
* Disables a given list of themes.
*
* @param array $theme_list
* An array of theme names.
*/
public function disable(array $theme_list);
/**
* Returns a list of all currently available themes.
*
* Retrieved from the database, if available and the site is not in
* maintenance mode; otherwise compiled freshly from the filesystem.
*
* @return array
* An associative array of the currently available themes. The keys are the
* themes' machine names and the values are objects having the following
* properties:
* - filename: The filepath and name of the .info.yml file.
* - name: The machine name of the theme.
* - status: 1 for enabled, 0 for disabled themes.
* - info: The contents of the .info.yml file.
* - stylesheets: A two dimensional array, using the first key for the
* media attribute (e.g. 'all'), the second for the name of the file
* (e.g. style.css). The value is a complete filepath (e.g.
* themes/bartik/style.css). Not set if no stylesheets are defined in the
* .info.yml file.
* - scripts: An associative array of JavaScripts, using the filename as key
* and the complete filepath as value. Not set if no scripts are defined
* in the .info.yml file.
* - prefix: The base theme engine prefix.
* - engine: The machine name of the theme engine.
* - base_theme: If this is a sub-theme, the machine name of the base theme
* defined in the .info.yml file. Otherwise, the element is not set.
* - base_themes: If this is a sub-theme, an associative array of the
* base-theme ancestors of this theme, starting with this theme's base
* theme, then the base theme's own base theme, etc. Each entry has an
* array key equal to the theme's machine name, and a value equal to the
* human-readable theme name; if a theme with matching machine name does
* not exist in the system, the value will instead be NULL (and since the
* system would not know whether that theme itself has a base theme, that
* will end the array of base themes). This is not set if the theme is not
* a sub-theme.
* - sub_themes: An associative array of themes on the system that are
* either direct sub-themes (that is, they declare this theme to be
* their base theme), direct sub-themes of sub-themes, etc. The keys are
* the themes' machine names, and the values are the themes'
* human-readable names. This element is not set if there are no themes on
* the system that declare this theme as their base theme.
*/
public function listInfo();
/**
* Resets the internal state of the theme handler.
*/
public function reset();
/**
* Helper function to scan and collect theme .info.yml data and their engines.
*
* @return array
* An associative array of themes information.
*/
public function rebuildThemeData();
/**
* Finds all the base themes for the specified theme.
*
* Themes can inherit templates and function implementations from earlier
* themes.
*
* @param array $themes
* An array of available themes.
* @param string $theme
* The name of the theme whose base we are looking for.
*
* @return array
* Returns an array of all of the theme's ancestors; the first element's
* value will be NULL if an error occurred.
*/
public function getBaseThemes(array $themes, $theme);
}

View File

@ -2461,117 +2461,11 @@ function system_rebuild_module_data() {
*
* @return
* An associative array of themes information.
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('theme_handler')->rebuildThemeData().
*/
function _system_rebuild_theme_data() {
// Find themes
$themes = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes');
// Allow modules to add further themes.
if ($module_themes = \Drupal::moduleHandler()->invokeAll('system_theme_info')) {
foreach ($module_themes as $name => $uri) {
// @see file_scan_directory()
$themes[$name] = (object) array(
'uri' => $uri,
'filename' => pathinfo($uri, PATHINFO_FILENAME),
'name' => $name,
);
}
}
// Find theme engines
$engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines');
// Set defaults for theme info.
$defaults = array(
'engine' => 'twig',
'regions' => array(
'sidebar_first' => 'Left sidebar',
'sidebar_second' => 'Right sidebar',
'content' => 'Content',
'header' => 'Header',
'footer' => 'Footer',
'highlighted' => 'Highlighted',
'help' => 'Help',
'page_top' => 'Page top',
'page_bottom' => 'Page bottom',
),
'description' => '',
'features' => _system_default_theme_features(),
'screenshot' => 'screenshot.png',
'php' => DRUPAL_MINIMUM_PHP,
'stylesheets' => array(),
'scripts' => array(),
);
$sub_themes = array();
// Read info files for each theme
foreach ($themes as $key => $theme) {
$themes[$key]->filename = $theme->uri;
$themes[$key]->info = \Drupal::service('info_parser')->parse($theme->uri) + $defaults;
// Skip this extension if its type is not theme.
if (!isset($themes[$key]->info['type']) || $themes[$key]->info['type'] != 'theme') {
unset($themes[$key]);
continue;
}
// Add the info file modification time, so it becomes available for
// contributed modules to use for ordering theme lists.
$themes[$key]->info['mtime'] = filemtime($theme->uri);
// Invoke hook_system_info_alter() to give installed modules a chance to
// modify the data in the .info.yml files if necessary.
$type = 'theme';
drupal_alter('system_info', $themes[$key]->info, $themes[$key], $type);
if (!empty($themes[$key]->info['base theme'])) {
$sub_themes[] = $key;
}
$engine = $themes[$key]->info['engine'];
if (isset($engines[$engine])) {
$themes[$key]->owner = $engines[$engine]->uri;
$themes[$key]->prefix = $engines[$engine]->name;
$themes[$key]->template = TRUE;
}
// Prefix stylesheets and scripts with module path.
$path = dirname($theme->uri);
$theme->info['stylesheets'] = _system_info_add_path($theme->info['stylesheets'], $path);
$theme->info['scripts'] = _system_info_add_path($theme->info['scripts'], $path);
// Give the screenshot proper path information.
if (!empty($themes[$key]->info['screenshot'])) {
$themes[$key]->info['screenshot'] = $path . '/' . $themes[$key]->info['screenshot'];
}
}
// Now that we've established all our master themes, go back and fill in data
// for subthemes.
foreach ($sub_themes as $key) {
$themes[$key]->base_themes = drupal_find_base_themes($themes, $key);
// Don't proceed if there was a problem with the root base theme.
if (!current($themes[$key]->base_themes)) {
continue;
}
$base_key = key($themes[$key]->base_themes);
foreach (array_keys($themes[$key]->base_themes) as $base_theme) {
$themes[$base_theme]->sub_themes[$key] = $themes[$key]->info['name'];
}
// Copy the 'owner' and 'engine' over if the top level theme uses a theme
// engine.
if (isset($themes[$base_key]->owner)) {
if (isset($themes[$base_key]->info['engine'])) {
$themes[$key]->info['engine'] = $themes[$base_key]->info['engine'];
$themes[$key]->owner = $themes[$base_key]->owner;
$themes[$key]->prefix = $themes[$base_key]->prefix;
}
else {
$themes[$key]->prefix = $key;
}
}
}
return $themes;
return \Drupal::service('theme_handler')->rebuildThemeData();
}
/**
@ -2607,41 +2501,6 @@ function system_rebuild_theme_data() {
return $themes;
}
/**
* Prefixes all values in an .info.yml file array with a given path.
*
* This helper function is mainly used to prefix all array values of an
* .info.yml file property with a single given path (to the module or theme);
* e.g., to prefix all values of the 'stylesheets' or 'scripts' properties with
* the file path to the defining module/theme.
*
* @param $info
* A nested array of data of an .info.yml file to be processed.
* @param $path
* A file path to prepend to each value in $info.
*
* @return
* The $info array with prefixed values.
*
* @see _system_rebuild_module_data()
* @see _system_rebuild_theme_data()
*/
function _system_info_add_path($info, $path) {
foreach ($info as $key => $value) {
// Recurse into nested values until we reach the deepest level.
if (is_array($value)) {
$info[$key] = _system_info_add_path($info[$key], $path);
}
// Unset the original value's key and set the new value with prefix, using
// the original value as key, so original values can still be looked up.
else {
unset($info[$key]);
$info[$value] = $path . '/' . $value;
}
}
return $info;
}
/**
* Returns an array of default theme features.
*/

View File

@ -0,0 +1,428 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Extension\ThemeHandlerTest.
*/
namespace Drupal\Tests\Core\Extension;
use Drupal\Core\Extension\InfoParser;
use Drupal\Core\Extension\ThemeHandler;
use Drupal\Tests\UnitTestCase;
/**
* Tests the theme handler.
*
* @group Drupal
* @group Theme
*
* @see \Drupal\Core\Extension\ThemeHandler
*/
class ThemeHandlerTest extends UnitTestCase {
/**
* The mocked route builder.
*
* @var \Drupal\Core\Routing\RouteBuilder|\PHPUnit_Framework_MockObject_MockObject
*/
protected $routeBuilder;
/**
* The mocked info parser.
*
* @var \Drupal\Core\Extension\InfoParserInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $infoParser;
/**
* The mocked cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $cacheBackend;
/**
* The mocked config factory.
*
* @var \Drupal\Core\Config\ConfigFactory|\PHPUnit_Framework_MockObject_MockObject
*/
protected $configFactory;
/**
* The mocked module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* The system listing info.
*
* @var \Drupal\Core\SystemListingInfo|\PHPUnit_Framework_MockObject_MockObject
*/
protected $systemListingInfo;
/**
* The tested theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandler|\Drupal\Tests\Core\Extension\TestThemeHandler
*/
protected $themeHandler;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Theme handler',
'description' => 'Tests the theme handler.',
'group' => 'Theme',
);
}
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->configFactory = $this->getConfigFactoryStub(array('system.theme' => array(), 'system.theme.disabled' => array()));
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface');
$this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder')
->disableOriginalConstructor()
->getMock();
$this->systemListingInfo = $this->getMockBuilder('Drupal\Core\SystemListingInfo')
->disableOriginalConstructor()
->getMock();
$this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->routeBuilder, $this->systemListingInfo);
}
/**
* Tests enabling a theme with a name longer than 50 chars.
*
* @expectedException \Drupal\Core\Extension\ExtensionNameLengthException
* @expectedExceptionMessage Theme name <em class="placeholder">thisNameIsFarTooLong0000000000000000000000000000051</em> is over the maximum allowed length of 50 characters.
*/
public function testThemeEnableWithTooLongName() {
$this->themeHandler->enable(array('thisNameIsFarTooLong0000000000000000000000000000051'));
}
/**
* Tests enabling a single theme.
*
* @see \Drupal\Core\Extension\ThemeHandler::enable()
*/
public function testEnableSingleTheme() {
$theme_list = array('theme_test');
$this->configFactory->get('system.theme')
->expects($this->once())
->method('set')
->with('enabled.theme_test', 0)
->will($this->returnSelf());
$this->configFactory->get('system.theme')
->expects($this->once())
->method('save');
$this->configFactory->get('system.theme.disabled')
->expects($this->once())
->method('clear')
->with('theme_test')
->will($this->returnSelf());
$this->configFactory->get('system.theme.disabled')
->expects($this->once())
->method('save');
$this->systemListingInfo->expects($this->any())
->method('scan')
->will($this->returnValue(array()));
// Ensure that the themes_enabled hook is fired.
$this->moduleHandler->expects($this->at(0))
->method('invokeAll')
->with('system_theme_info')
->will($this->returnValue(array()));
$this->moduleHandler->expects($this->at(1))
->method('invokeAll')
->with('themes_enabled', array($theme_list));
$this->themeHandler->enable($theme_list);
$this->assertTrue($this->themeHandler->clearedCssCache);
$this->assertTrue($this->themeHandler->registryRebuild);
$this->assertTrue($this->themeHandler->installedDefaultConfig['theme_test']);
}
/**
* Ensures that enabling a theme does clear the theme info listing.
*
* @see \Drupal\Core\Extension\ThemeHandler::listInfo()
*/
public function testEnableAndListInfo() {
$this->configFactory->get('system.theme')
->expects($this->exactly(2))
->method('set')
->will($this->returnSelf());
$this->configFactory->get('system.theme.disabled')
->expects($this->exactly(2))
->method('clear')
->will($this->returnSelf());
$this->systemListingInfo->expects($this->any())
->method('scan')
->will($this->returnValue(array()));
$this->themeHandler->enable(array('bartik'));
$this->themeHandler->systemList['bartik'] = (object) array(
'name' => 'bartik',
'info' => array(
'stylesheets' => array(
'all' => array(
'css/layout.css',
'css/style.css',
'css/colors.css',
),
),
'scripts' => array(
'example' => 'theme.js',
),
'engine' => 'twig',
'base theme' => 'stark',
),
);
$list_info = $this->themeHandler->listInfo();
$this->assertCount(1, $list_info);
$this->assertEquals($this->themeHandler->systemList['bartik']->info['stylesheets'], $list_info['bartik']->stylesheets);
$this->assertEquals($this->themeHandler->systemList['bartik']->scripts, $list_info['bartik']->scripts);
$this->assertEquals('twig', $list_info['bartik']->engine);
$this->assertEquals('stark', $list_info['bartik']->base_theme);
$this->assertEquals(0, $list_info['bartik']->status);
$this->themeHandler->systemList['seven'] = (object) array(
'name' => 'seven',
'info' => array(
'stylesheets' => array(
'screen' => array(
'style.css',
),
),
'scripts' => array(),
),
'status' => 1,
);
$this->themeHandler->enable(array('seven'));
$list_info = $this->themeHandler->listInfo();
$this->assertCount(2, $list_info);
$this->assertEquals($this->themeHandler->systemList['seven']->info['stylesheets'], $list_info['seven']->stylesheets);
$this->assertEquals(1, $list_info['seven']->status);
}
/**
* Tests rebuilding the theme data.
*
* @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData()
*/
public function testRebuildThemeData() {
$this->systemListingInfo->expects($this->at(0))
->method('scan')
->with($this->anything(), 'themes', 'name', 1)
->will($this->returnValue(array(
'seven' => (object) array(
'name' => 'seven',
'uri' => DRUPAL_ROOT . '/core/themes/seven/seven.info.yml',
),
)));
$this->infoParser->expects($this->once())
->method('parse')
->with(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml')
->will($this->returnCallback(function ($file) {
$info_parser = new InfoParser();
return $info_parser->parse($file);
}));
$this->moduleHandler->expects($this->once())
->method('alter');
$theme_data = $this->themeHandler->rebuildThemeData();
$this->assertCount(1, $theme_data);
$info = $theme_data['seven'];
// Ensure some basic properties.
$this->assertInstanceOf('stdClass', $info);
$this->assertEquals('seven', $info->name);
$this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', $info->uri);
$this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', $info->filename);
$this->assertEquals('twig', $info->info['engine']);
$this->assertEquals(array(), $info->info['scripts']);
// Ensure that the css paths are set with the proper prefix.
$this->assertEquals(array(
'screen' => array(
'style.css' => DRUPAL_ROOT . '/core/themes/seven/style.css',
'css/components/buttons.css' => DRUPAL_ROOT . '/core/themes/seven/css/components/buttons.css',
'css/components/buttons.theme.css' => DRUPAL_ROOT . '/core/themes/seven/css/components/buttons.theme.css',
),
), $info->info['stylesheets']);
$this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/screenshot.png', $info->info['screenshot']);
}
/**
* Tests getting the base themes for a set a defines themes.
*
* @param array $themes
* An array of available themes, keyed by the theme name.
* @param string $theme
* The theme name to find all its base themes.
* @param array $expected
* The expected base themes.
*
* @dataProvider providerTestGetBaseThemes
*/
public function testGetBaseThemes(array $themes, $theme, array $expected) {
$base_themes = $this->themeHandler->getBaseThemes($themes, $theme);
$this->assertEquals($expected, $base_themes);
}
/**
* Provides test data for testGetBaseThemes.
*
* @return array
* An array of theme test data.
*/
public function providerTestGetBaseThemes() {
$data = array();
// Tests a theme without any base theme.
$themes = array();
$themes['test_1'] = (object) array(
'name' => 'test_1',
'info' => array(
'name' => 'test_1',
),
);
$data[] = array($themes, 'test_1', array());
// Tests a theme with a non existing base theme.
$themes = array();
$themes['test_1'] = (object) array(
'name' => 'test_1',
'info' => array(
'name' => 'test_1',
'base theme' => 'test_2',
),
);
$data[] = array($themes, 'test_1', array('test_2' => NULL));
// Tests a theme with a single existing base theme.
$themes = array();
$themes['test_1'] = (object) array(
'name' => 'test_1',
'info' => array(
'name' => 'test_1',
'base theme' => 'test_2',
),
);
$themes['test_2'] = (object) array(
'name' => 'test_2',
'info' => array(
'name' => 'test_2',
),
);
$data[] = array($themes, 'test_1', array('test_2' => 'test_2'));
// Tests a theme with multiple base themes.
$themes = array();
$themes['test_1'] = (object) array(
'name' => 'test_1',
'info' => array(
'name' => 'test_1',
'base theme' => 'test_2',
),
);
$themes['test_2'] = (object) array(
'name' => 'test_2',
'info' => array(
'name' => 'test_2',
'base theme' => 'test_3',
),
);
$themes['test_3'] = (object) array(
'name' => 'test_3',
'info' => array(
'name' => 'test_3',
),
);
$data[] = array(
$themes,
'test_1',
array('test_2' => 'test_2', 'test_3' => 'test_3'),
);
return $data;
}
}
/**
* Extends the default theme handler to mock some drupal_ methods.
*/
class TestThemeHandler extends ThemeHandler {
/**
* {@inheritdoc}
*/
protected function clearCssCache() {
$this->clearedCssCache = TRUE;
}
/**
* {@inheritdoc}
*/
protected function themeRegistryRebuild() {
$this->registryRebuild = TRUE;
}
/**
* {@inheritdoc}
*/
protected function configInstallDefaultConfig($theme) {
$this->installedDefaultConfig[$theme] = TRUE;
}
/**
* {@inheritdoc}
*/
protected function systemThemeList() {
return $this->systemList;
}
/**
* {@inheritdoc}
*/
protected function systemListReset() {
}
}
if (!defined('DRUPAL_EXTENSION_NAME_MAX_LENGTH')) {
define('DRUPAL_EXTENSION_NAME_MAX_LENGTH', 50);
}
if (!defined('DRUPAL_PHP_FUNCTION_PATTERN')) {
define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
}
if (!defined('DRUPAL_ROOT')) {
define('DRUPAL_ROOT', dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))));
}
if (!defined('DRUPAL_MINIMUM_PHP')) {
define('DRUPAL_MINIMUM_PHP', '5.3.10');
}