diff core/lib/Drupal/Core/Extension/ThemeHandler.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 129ea1e6d783
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php	Wed Nov 29 16:09:58 2017 +0000
@@ -0,0 +1,507 @@
+<?php
+
+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 = [
+    '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 = [];
+      $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 ?: [];
+        $themes = \Drupal::state()->get('system.theme.data', []);
+      }
+      foreach ($themes as $theme) {
+        $this->addTheme($theme);
+      }
+    }
+    return $this->list;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addTheme(Extension $theme) {
+    if (!empty($theme->info['libraries'])) {
+      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() {
+    $extension_config = $this->configFactory->get('core.extension');
+    $installed = $extension_config->get('theme');
+    // Only refresh the info if a theme has been installed. Modules are
+    // installed before themes by the installer and this method is called during
+    // module installation.
+    if (empty($installed) && empty($this->list)) {
+      return;
+    }
+
+    $this->reset();
+    // @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') ?: [];
+
+    // Set defaults for theme info.
+    $defaults = [
+      'engine' => 'twig',
+      'base theme' => 'stable',
+      'regions' => [
+        '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' => [],
+    ];
+
+    $sub_themes = [];
+    $files_theme = [];
+    $files_theme_engine = [];
+    // 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 = []) {
+    if (!isset($themes[$theme]->info['base theme'])) {
+      return [];
+    }
+
+    $base_key = $themes[$theme]->info['base theme'];
+    // Does the base theme exist?
+    if (!isset($themes[$base_key])) {
+      return [$base_key => NULL];
+    }
+
+    $current_base_theme = [$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 [$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 = [];
+    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;
+  }
+
+}