Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
15.00% |
3 / 20 |
CRAP | |
51.08% |
71 / 139 |
ThemeHandler | |
0.00% |
0 / 1 |
|
15.00% |
3 / 20 |
368.58 | |
51.08% |
71 / 139 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
7 / 7 |
|||
getDefault | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
setDefault | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 7 |
|||
install | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
uninstall | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
listInfo | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 10 |
|||
addTheme | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
refreshInfo | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 9 |
|||
reset | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
rebuildThemeData | |
100.00% |
1 / 1 |
11 | |
100.00% |
49 / 49 |
|||
getBaseThemes | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
doGetBaseThemes | |
0.00% |
0 / 1 |
6.10 | |
85.71% |
12 / 14 |
|||
getExtensionDiscovery | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
getName | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
systemListReset | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
systemThemeList | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getThemeDirectories | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
themeExists | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getTheme | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
hasUi | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 7 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Extension\ThemeHandler. | |
*/ | |
namespace Drupal\Core\Extension; | |
use Drupal\Core\Config\ConfigFactoryInterface; | |
use Drupal\Core\State\StateInterface; | |
/** | |
* Default theme handler using the config system to store installation statuses. | |
*/ | |
class ThemeHandler implements ThemeHandlerInterface { | |
/** | |
* Contains the features enabled for themes by default. | |
* | |
* @var array | |
* | |
* @see _system_default_theme_features() | |
*/ | |
protected $defaultFeatures = array( | |
'favicon', | |
'logo', | |
'node_user_picture', | |
'comment_user_picture', | |
'comment_user_verification', | |
); | |
/** | |
* A list of all currently available themes. | |
* | |
* @var array | |
*/ | |
protected $list; | |
/** | |
* The config factory to get the installed themes. | |
* | |
* @var \Drupal\Core\Config\ConfigFactoryInterface | |
*/ | |
protected $configFactory; | |
/** | |
* The module handler to fire themes_installed/themes_uninstalled hooks. | |
* | |
* @var \Drupal\Core\Extension\ModuleHandlerInterface | |
*/ | |
protected $moduleHandler; | |
/** | |
* The state backend. | |
* | |
* @var \Drupal\Core\State\StateInterface | |
*/ | |
protected $state; | |
/** | |
* The config installer to install configuration. | |
* | |
* @var \Drupal\Core\Config\ConfigInstallerInterface | |
*/ | |
protected $configInstaller; | |
/** | |
* The info parser to parse the theme.info.yml files. | |
* | |
* @var \Drupal\Core\Extension\InfoParserInterface | |
*/ | |
protected $infoParser; | |
/** | |
* A logger instance. | |
* | |
* @var \Psr\Log\LoggerInterface | |
*/ | |
protected $logger; | |
/** | |
* The route builder to rebuild the routes if a theme is installed. | |
* | |
* @var \Drupal\Core\Routing\RouteBuilderInterface | |
*/ | |
protected $routeBuilder; | |
/** | |
* An extension discovery instance. | |
* | |
* @var \Drupal\Core\Extension\ExtensionDiscovery | |
*/ | |
protected $extensionDiscovery; | |
/** | |
* The CSS asset collection optimizer service. | |
* | |
* @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface | |
*/ | |
protected $cssCollectionOptimizer; | |
/** | |
* The config manager used to uninstall a theme. | |
* | |
* @var \Drupal\Core\Config\ConfigManagerInterface | |
*/ | |
protected $configManager; | |
/** | |
* The app root. | |
* | |
* @var string | |
*/ | |
protected $root; | |
/** | |
* Constructs a new ThemeHandler. | |
* | |
* @param string $root | |
* The app root. | |
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | |
* The config factory to get the installed themes. | |
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
* The module handler to fire themes_installed/themes_uninstalled hooks. | |
* @param \Drupal\Core\State\StateInterface $state | |
* The state store. | |
* @param \Drupal\Core\Extension\InfoParserInterface $info_parser | |
* The info parser to parse the theme.info.yml files. | |
* @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery | |
* (optional) A extension discovery instance (for unit tests). | |
*/ | |
public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ExtensionDiscovery $extension_discovery = NULL) { | |
$this->root = $root; | |
$this->configFactory = $config_factory; | |
$this->moduleHandler = $module_handler; | |
$this->state = $state; | |
$this->infoParser = $info_parser; | |
$this->extensionDiscovery = $extension_discovery; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getDefault() { | |
return $this->configFactory->get('system.theme')->get('default'); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function setDefault($name) { | |
$list = $this->listInfo(); | |
if (!isset($list[$name])) { | |
throw new \InvalidArgumentException("$name theme is not installed."); | |
} | |
$this->configFactory->getEditable('system.theme') | |
->set('default', $name) | |
->save(); | |
return $this; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function install(array $theme_list, $install_dependencies = TRUE) { | |
// We keep the old install() method as BC layer but redirect directly to the | |
// theme installer. | |
return \Drupal::service('theme_installer')->install($theme_list, $install_dependencies); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function uninstall(array $theme_list) { | |
// We keep the old uninstall() method as BC layer but redirect directly to | |
// the theme installer. | |
\Drupal::service('theme_installer')->uninstall($theme_list); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function listInfo() { | |
if (!isset($this->list)) { | |
$this->list = array(); | |
$themes = $this->systemThemeList(); | |
// @todo Ensure that systemThemeList() does not contain an empty list | |
// during the batch installer, see https://www.drupal.org/node/2322619. | |
if (empty($themes)) { | |
$this->refreshInfo(); | |
$this->list = $this->list ?: array(); | |
$themes = \Drupal::state()->get('system.theme.data', array()); | |
} | |
foreach ($themes as $theme) { | |
$this->addTheme($theme); | |
} | |
} | |
return $this->list; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function addTheme(Extension $theme) { | |
foreach ($theme->info['libraries'] as $library => $name) { | |
$theme->libraries[$library] = $name; | |
} | |
if (isset($theme->info['engine'])) { | |
$theme->engine = $theme->info['engine']; | |
} | |
if (isset($theme->info['base theme'])) { | |
$theme->base_theme = $theme->info['base theme']; | |
} | |
$this->list[$theme->getName()] = $theme; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function refreshInfo() { | |
$this->reset(); | |
$extension_config = $this->configFactory->get('core.extension'); | |
$installed = $extension_config->get('theme'); | |
// @todo Avoid re-scanning all themes by retaining the original (unaltered) | |
// theme info somewhere. | |
$list = $this->rebuildThemeData(); | |
foreach ($list as $name => $theme) { | |
if (isset($installed[$name])) { | |
$this->addTheme($theme); | |
} | |
} | |
$this->state->set('system.theme.data', $this->list); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function reset() { | |
$this->systemListReset(); | |
$this->list = NULL; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function rebuildThemeData() { | |
$listing = $this->getExtensionDiscovery(); | |
$themes = $listing->scan('theme'); | |
$engines = $listing->scan('theme_engine'); | |
$extension_config = $this->configFactory->get('core.extension'); | |
$installed = $extension_config->get('theme') ?: array(); | |
// Set defaults for theme info. | |
$defaults = array( | |
'engine' => 'twig', | |
'base theme' => 'stable', | |
'regions' => array( | |
'sidebar_first' => 'Left sidebar', | |
'sidebar_second' => 'Right sidebar', | |
'content' => 'Content', | |
'header' => 'Header', | |
'primary_menu' => 'Primary menu', | |
'secondary_menu' => 'Secondary menu', | |
'footer' => 'Footer', | |
'highlighted' => 'Highlighted', | |
'help' => 'Help', | |
'page_top' => 'Page top', | |
'page_bottom' => 'Page bottom', | |
'breadcrumb' => 'Breadcrumb', | |
), | |
'description' => '', | |
'features' => $this->defaultFeatures, | |
'screenshot' => 'screenshot.png', | |
'php' => DRUPAL_MINIMUM_PHP, | |
'libraries' => array(), | |
); | |
$sub_themes = array(); | |
$files_theme = array(); | |
$files_theme_engine = array(); | |
// Read info files for each theme. | |
foreach ($themes as $key => $theme) { | |
// @todo Remove all code that relies on the $status property. | |
$theme->status = (int) isset($installed[$key]); | |
$theme->info = $this->infoParser->parse($theme->getPathname()) + $defaults; | |
// Remove the default Stable base theme when 'base theme: false' is set in | |
// a theme .info.yml file. | |
if ($theme->info['base theme'] === FALSE) { | |
unset($theme->info['base theme']); | |
} | |
// Add the info file modification time, so it becomes available for | |
// contributed modules to use for ordering theme lists. | |
$theme->info['mtime'] = $theme->getMTime(); | |
// Invoke hook_system_info_alter() to give installed modules a chance to | |
// modify the data in the .info.yml files if necessary. | |
// @todo Remove $type argument, obsolete with $theme->getType(). | |
$type = 'theme'; | |
$this->moduleHandler->alter('system_info', $theme->info, $theme, $type); | |
if (!empty($theme->info['base theme'])) { | |
$sub_themes[] = $key; | |
// Add the base theme as a proper dependency. | |
$themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme']; | |
} | |
// Defaults to 'twig' (see $defaults above). | |
$engine = $theme->info['engine']; | |
if (isset($engines[$engine])) { | |
$theme->owner = $engines[$engine]->getExtensionPathname(); | |
$theme->prefix = $engines[$engine]->getName(); | |
$files_theme_engine[$engine] = $engines[$engine]->getPathname(); | |
} | |
// Prefix screenshot with theme path. | |
if (!empty($theme->info['screenshot'])) { | |
$theme->info['screenshot'] = $theme->getPath() . '/' . $theme->info['screenshot']; | |
} | |
$files_theme[$key] = $theme->getPathname(); | |
} | |
// Build dependencies. | |
// @todo Move into a generic ExtensionHandler base class. | |
// @see https://www.drupal.org/node/2208429 | |
$themes = $this->moduleHandler->buildModuleDependencies($themes); | |
// Store filenames to allow system_list() and drupal_get_filename() to | |
// retrieve them for themes and theme engines without having to scan the | |
// filesystem. | |
$this->state->set('system.theme.files', $files_theme); | |
$this->state->set('system.theme_engine.files', $files_theme_engine); | |
// After establishing the full list of available themes, fill in data for | |
// sub-themes. | |
foreach ($sub_themes as $key) { | |
$sub_theme = $themes[$key]; | |
// The $base_themes property is optional; only set for sub themes. | |
// @see ThemeHandlerInterface::listInfo() | |
$sub_theme->base_themes = $this->getBaseThemes($themes, $key); | |
// empty() cannot be used here, since ThemeHandler::doGetBaseThemes() adds | |
// the key of a base theme with a value of NULL in case it is not found, | |
// in order to prevent needless iterations. | |
if (!current($sub_theme->base_themes)) { | |
continue; | |
} | |
// Determine the root base theme. | |
$root_key = key($sub_theme->base_themes); | |
// Build the list of sub-themes for each of the theme's base themes. | |
foreach (array_keys($sub_theme->base_themes) as $base_theme) { | |
$themes[$base_theme]->sub_themes[$key] = $sub_theme->info['name']; | |
} | |
// Add the theme engine info from the root base theme. | |
if (isset($themes[$root_key]->owner)) { | |
$sub_theme->info['engine'] = $themes[$root_key]->info['engine']; | |
$sub_theme->owner = $themes[$root_key]->owner; | |
$sub_theme->prefix = $themes[$root_key]->prefix; | |
} | |
} | |
return $themes; | |
} | |
/** | |
* {@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->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme; | |
} | |
// If we get here, then this is our parent theme. | |
return $current_base_theme; | |
} | |
/** | |
* Returns an extension discovery object. | |
* | |
* @return \Drupal\Core\Extension\ExtensionDiscovery | |
* The extension discovery object. | |
*/ | |
protected function getExtensionDiscovery() { | |
if (!isset($this->extensionDiscovery)) { | |
$this->extensionDiscovery = new ExtensionDiscovery($this->root); | |
} | |
return $this->extensionDiscovery; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getName($theme) { | |
$themes = $this->listInfo(); | |
if (!isset($themes[$theme])) { | |
throw new \InvalidArgumentException("Requested the name of a non-existing theme $theme"); | |
} | |
return $themes[$theme]->info['name']; | |
} | |
/** | |
* Wraps system_list_reset(). | |
*/ | |
protected function systemListReset() { | |
system_list_reset(); | |
} | |
/** | |
* Wraps system_list(). | |
* | |
* @return array | |
* A list of themes keyed by name. | |
*/ | |
protected function systemThemeList() { | |
return system_list('theme'); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getThemeDirectories() { | |
$dirs = array(); | |
foreach ($this->listInfo() as $name => $theme) { | |
$dirs[$name] = $this->root . '/' . $theme->getPath(); | |
} | |
return $dirs; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function themeExists($theme) { | |
$themes = $this->listInfo(); | |
return isset($themes[$theme]); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getTheme($name) { | |
$themes = $this->listInfo(); | |
if (isset($themes[$name])) { | |
return $themes[$name]; | |
} | |
throw new \InvalidArgumentException(sprintf('The theme %s does not exist.', $name)); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function hasUi($name) { | |
$themes = $this->listInfo(); | |
if (isset($themes[$name])) { | |
if (!empty($themes[$name]->info['hidden'])) { | |
$theme_config = $this->configFactory->get('system.theme'); | |
return $name == $theme_config->get('default') || $name == $theme_config->get('admin'); | |
} | |
return TRUE; | |
} | |
return FALSE; | |
} | |
} |