Chris@0: root = $root; Chris@0: $this->configFactory = $config_factory; Chris@0: $this->moduleHandler = $module_handler; Chris@0: $this->state = $state; Chris@0: $this->infoParser = $info_parser; Chris@0: $this->extensionDiscovery = $extension_discovery; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDefault() { Chris@0: return $this->configFactory->get('system.theme')->get('default'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setDefault($name) { Chris@0: $list = $this->listInfo(); Chris@0: if (!isset($list[$name])) { Chris@0: throw new \InvalidArgumentException("$name theme is not installed."); Chris@0: } Chris@0: $this->configFactory->getEditable('system.theme') Chris@0: ->set('default', $name) Chris@0: ->save(); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function install(array $theme_list, $install_dependencies = TRUE) { Chris@0: // We keep the old install() method as BC layer but redirect directly to the Chris@0: // theme installer. Chris@0: return \Drupal::service('theme_installer')->install($theme_list, $install_dependencies); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function uninstall(array $theme_list) { Chris@0: // We keep the old uninstall() method as BC layer but redirect directly to Chris@0: // the theme installer. Chris@0: \Drupal::service('theme_installer')->uninstall($theme_list); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function listInfo() { Chris@0: if (!isset($this->list)) { Chris@0: $this->list = []; Chris@0: $themes = $this->systemThemeList(); Chris@0: // @todo Ensure that systemThemeList() does not contain an empty list Chris@0: // during the batch installer, see https://www.drupal.org/node/2322619. Chris@0: if (empty($themes)) { Chris@0: $this->refreshInfo(); Chris@0: $this->list = $this->list ?: []; Chris@0: $themes = \Drupal::state()->get('system.theme.data', []); Chris@0: } Chris@0: foreach ($themes as $theme) { Chris@0: $this->addTheme($theme); Chris@0: } Chris@0: } Chris@0: return $this->list; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addTheme(Extension $theme) { Chris@0: if (!empty($theme->info['libraries'])) { Chris@0: foreach ($theme->info['libraries'] as $library => $name) { Chris@0: $theme->libraries[$library] = $name; Chris@0: } Chris@0: } Chris@0: if (isset($theme->info['engine'])) { Chris@0: $theme->engine = $theme->info['engine']; Chris@0: } Chris@0: if (isset($theme->info['base theme'])) { Chris@0: $theme->base_theme = $theme->info['base theme']; Chris@0: } Chris@0: $this->list[$theme->getName()] = $theme; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function refreshInfo() { Chris@0: $extension_config = $this->configFactory->get('core.extension'); Chris@0: $installed = $extension_config->get('theme'); Chris@0: // Only refresh the info if a theme has been installed. Modules are Chris@0: // installed before themes by the installer and this method is called during Chris@0: // module installation. Chris@0: if (empty($installed) && empty($this->list)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $this->reset(); Chris@0: // @todo Avoid re-scanning all themes by retaining the original (unaltered) Chris@0: // theme info somewhere. Chris@0: $list = $this->rebuildThemeData(); Chris@0: foreach ($list as $name => $theme) { Chris@0: if (isset($installed[$name])) { Chris@0: $this->addTheme($theme); Chris@0: } Chris@0: } Chris@0: $this->state->set('system.theme.data', $this->list); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function reset() { Chris@0: $this->systemListReset(); Chris@0: $this->list = NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function rebuildThemeData() { Chris@0: $listing = $this->getExtensionDiscovery(); Chris@0: $themes = $listing->scan('theme'); Chris@0: $engines = $listing->scan('theme_engine'); Chris@0: $extension_config = $this->configFactory->get('core.extension'); Chris@0: $installed = $extension_config->get('theme') ?: []; Chris@0: Chris@0: // Set defaults for theme info. Chris@0: $defaults = [ Chris@0: 'engine' => 'twig', Chris@0: 'base theme' => 'stable', Chris@0: 'regions' => [ Chris@0: 'sidebar_first' => 'Left sidebar', Chris@0: 'sidebar_second' => 'Right sidebar', Chris@0: 'content' => 'Content', Chris@0: 'header' => 'Header', Chris@0: 'primary_menu' => 'Primary menu', Chris@0: 'secondary_menu' => 'Secondary menu', Chris@0: 'footer' => 'Footer', Chris@0: 'highlighted' => 'Highlighted', Chris@0: 'help' => 'Help', Chris@0: 'page_top' => 'Page top', Chris@0: 'page_bottom' => 'Page bottom', Chris@0: 'breadcrumb' => 'Breadcrumb', Chris@0: ], Chris@0: 'description' => '', Chris@0: 'features' => $this->defaultFeatures, Chris@0: 'screenshot' => 'screenshot.png', Chris@0: 'php' => DRUPAL_MINIMUM_PHP, Chris@0: 'libraries' => [], Chris@0: ]; Chris@0: Chris@0: $sub_themes = []; Chris@0: $files_theme = []; Chris@0: $files_theme_engine = []; Chris@0: // Read info files for each theme. Chris@0: foreach ($themes as $key => $theme) { Chris@0: // @todo Remove all code that relies on the $status property. Chris@0: $theme->status = (int) isset($installed[$key]); Chris@0: Chris@0: $theme->info = $this->infoParser->parse($theme->getPathname()) + $defaults; Chris@0: // Remove the default Stable base theme when 'base theme: false' is set in Chris@0: // a theme .info.yml file. Chris@0: if ($theme->info['base theme'] === FALSE) { Chris@0: unset($theme->info['base theme']); Chris@0: } Chris@0: Chris@0: // Add the info file modification time, so it becomes available for Chris@0: // contributed modules to use for ordering theme lists. Chris@0: $theme->info['mtime'] = $theme->getMTime(); Chris@0: Chris@0: // Invoke hook_system_info_alter() to give installed modules a chance to Chris@0: // modify the data in the .info.yml files if necessary. Chris@0: // @todo Remove $type argument, obsolete with $theme->getType(). Chris@0: $type = 'theme'; Chris@0: $this->moduleHandler->alter('system_info', $theme->info, $theme, $type); Chris@0: Chris@0: if (!empty($theme->info['base theme'])) { Chris@0: $sub_themes[] = $key; Chris@0: // Add the base theme as a proper dependency. Chris@0: $themes[$key]->info['dependencies'][] = $themes[$key]->info['base theme']; Chris@0: } Chris@0: Chris@0: // Defaults to 'twig' (see $defaults above). Chris@0: $engine = $theme->info['engine']; Chris@0: if (isset($engines[$engine])) { Chris@0: $theme->owner = $engines[$engine]->getExtensionPathname(); Chris@0: $theme->prefix = $engines[$engine]->getName(); Chris@0: $files_theme_engine[$engine] = $engines[$engine]->getPathname(); Chris@0: } Chris@0: Chris@0: // Prefix screenshot with theme path. Chris@0: if (!empty($theme->info['screenshot'])) { Chris@0: $theme->info['screenshot'] = $theme->getPath() . '/' . $theme->info['screenshot']; Chris@0: } Chris@0: Chris@0: $files_theme[$key] = $theme->getPathname(); Chris@0: } Chris@0: // Build dependencies. Chris@0: // @todo Move into a generic ExtensionHandler base class. Chris@0: // @see https://www.drupal.org/node/2208429 Chris@0: $themes = $this->moduleHandler->buildModuleDependencies($themes); Chris@0: Chris@0: // Store filenames to allow system_list() and drupal_get_filename() to Chris@0: // retrieve them for themes and theme engines without having to scan the Chris@0: // filesystem. Chris@0: $this->state->set('system.theme.files', $files_theme); Chris@0: $this->state->set('system.theme_engine.files', $files_theme_engine); Chris@0: Chris@0: // After establishing the full list of available themes, fill in data for Chris@0: // sub-themes. Chris@0: foreach ($sub_themes as $key) { Chris@0: $sub_theme = $themes[$key]; Chris@0: // The $base_themes property is optional; only set for sub themes. Chris@0: // @see ThemeHandlerInterface::listInfo() Chris@0: $sub_theme->base_themes = $this->getBaseThemes($themes, $key); Chris@0: // empty() cannot be used here, since ThemeHandler::doGetBaseThemes() adds Chris@0: // the key of a base theme with a value of NULL in case it is not found, Chris@0: // in order to prevent needless iterations. Chris@0: if (!current($sub_theme->base_themes)) { Chris@0: continue; Chris@0: } Chris@0: // Determine the root base theme. Chris@0: $root_key = key($sub_theme->base_themes); Chris@0: // Build the list of sub-themes for each of the theme's base themes. Chris@0: foreach (array_keys($sub_theme->base_themes) as $base_theme) { Chris@0: $themes[$base_theme]->sub_themes[$key] = $sub_theme->info['name']; Chris@0: } Chris@0: // Add the theme engine info from the root base theme. Chris@0: if (isset($themes[$root_key]->owner)) { Chris@0: $sub_theme->info['engine'] = $themes[$root_key]->info['engine']; Chris@0: $sub_theme->owner = $themes[$root_key]->owner; Chris@0: $sub_theme->prefix = $themes[$root_key]->prefix; Chris@0: } Chris@0: } Chris@0: Chris@0: return $themes; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getBaseThemes(array $themes, $theme) { Chris@0: return $this->doGetBaseThemes($themes, $theme); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finds the base themes for the specific theme. Chris@0: * Chris@0: * @param array $themes Chris@0: * An array of available themes. Chris@0: * @param string $theme Chris@0: * The name of the theme whose base we are looking for. Chris@0: * @param array $used_themes Chris@0: * (optional) A recursion parameter preventing endless loops. Defaults to Chris@0: * an empty array. Chris@0: * Chris@0: * @return array Chris@0: * An array of base themes. Chris@0: */ Chris@0: protected function doGetBaseThemes(array $themes, $theme, $used_themes = []) { Chris@0: if (!isset($themes[$theme]->info['base theme'])) { Chris@0: return []; Chris@0: } Chris@0: Chris@0: $base_key = $themes[$theme]->info['base theme']; Chris@0: // Does the base theme exist? Chris@0: if (!isset($themes[$base_key])) { Chris@0: return [$base_key => NULL]; Chris@0: } Chris@0: Chris@0: $current_base_theme = [$base_key => $themes[$base_key]->info['name']]; Chris@0: Chris@0: // Is the base theme itself a child of another theme? Chris@0: if (isset($themes[$base_key]->info['base theme'])) { Chris@0: // Do we already know the base themes of this theme? Chris@0: if (isset($themes[$base_key]->base_themes)) { Chris@0: return $themes[$base_key]->base_themes + $current_base_theme; Chris@0: } Chris@0: // Prevent loops. Chris@0: if (!empty($used_themes[$base_key])) { Chris@0: return [$base_key => NULL]; Chris@0: } Chris@0: $used_themes[$base_key] = TRUE; Chris@0: return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme; Chris@0: } Chris@0: // If we get here, then this is our parent theme. Chris@0: return $current_base_theme; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an extension discovery object. Chris@0: * Chris@0: * @return \Drupal\Core\Extension\ExtensionDiscovery Chris@0: * The extension discovery object. Chris@0: */ Chris@0: protected function getExtensionDiscovery() { Chris@0: if (!isset($this->extensionDiscovery)) { Chris@0: $this->extensionDiscovery = new ExtensionDiscovery($this->root); Chris@0: } Chris@0: return $this->extensionDiscovery; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getName($theme) { Chris@0: $themes = $this->listInfo(); Chris@0: if (!isset($themes[$theme])) { Chris@0: throw new \InvalidArgumentException("Requested the name of a non-existing theme $theme"); Chris@0: } Chris@0: return $themes[$theme]->info['name']; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Wraps system_list_reset(). Chris@0: */ Chris@0: protected function systemListReset() { Chris@0: system_list_reset(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Wraps system_list(). Chris@0: * Chris@0: * @return array Chris@0: * A list of themes keyed by name. Chris@0: */ Chris@0: protected function systemThemeList() { Chris@0: return system_list('theme'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getThemeDirectories() { Chris@0: $dirs = []; Chris@0: foreach ($this->listInfo() as $name => $theme) { Chris@0: $dirs[$name] = $this->root . '/' . $theme->getPath(); Chris@0: } Chris@0: return $dirs; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function themeExists($theme) { Chris@0: $themes = $this->listInfo(); Chris@0: return isset($themes[$theme]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getTheme($name) { Chris@0: $themes = $this->listInfo(); Chris@0: if (isset($themes[$name])) { Chris@0: return $themes[$name]; Chris@0: } Chris@0: throw new \InvalidArgumentException(sprintf('The theme %s does not exist.', $name)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasUi($name) { Chris@0: $themes = $this->listInfo(); Chris@0: if (isset($themes[$name])) { Chris@0: if (!empty($themes[$name]->info['hidden'])) { Chris@0: $theme_config = $this->configFactory->get('system.theme'); Chris@0: return $name == $theme_config->get('default') || $name == $theme_config->get('admin'); Chris@0: } Chris@0: return TRUE; Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: }