Issue #2659940 by almaudoh, alexpott, phenaproxima, Jo Fitzgerald, markcarver, oriol_e9g, dawehner, dsnopek, jibran, larowlan: Extension System, Part III: ThemeExtensionList and ThemeEngineExtensionList
							parent
							
								
									bab4f6d3f3
								
							
						
					
					
						commit
						10722cd872
					
				| 
						 | 
				
			
			@ -517,6 +517,12 @@ services:
 | 
			
		|||
  extension.list.profile:
 | 
			
		||||
    class: Drupal\Core\Extension\ProfileExtensionList
 | 
			
		||||
    arguments: ['@app.root', 'profile', '@cache.default', '@info_parser', '@module_handler', '@state', '%install_profile%']
 | 
			
		||||
  extension.list.theme:
 | 
			
		||||
    class: Drupal\Core\Extension\ThemeExtensionList
 | 
			
		||||
    arguments: ['@app.root', 'theme', '@cache.default', '@info_parser', '@module_handler', '@state', '@config.factory', '@extension.list.theme_engine', '%install_profile%']
 | 
			
		||||
  extension.list.theme_engine:
 | 
			
		||||
    class: Drupal\Core\Extension\ThemeEngineExtensionList
 | 
			
		||||
    arguments: ['@app.root', 'theme_engine', '@cache.default', '@info_parser', '@module_handler', '@state', '%install_profile%']
 | 
			
		||||
  content_uninstall_validator:
 | 
			
		||||
    class: Drupal\Core\Entity\ContentUninstallValidator
 | 
			
		||||
    tags:
 | 
			
		||||
| 
						 | 
				
			
			@ -531,7 +537,7 @@ services:
 | 
			
		|||
    lazy: true
 | 
			
		||||
  theme_handler:
 | 
			
		||||
    class: Drupal\Core\Extension\ThemeHandler
 | 
			
		||||
    arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser']
 | 
			
		||||
    arguments: ['@app.root', '@config.factory', '@extension.list.theme']
 | 
			
		||||
  theme_installer:
 | 
			
		||||
    class: Drupal\Core\Extension\ThemeInstaller
 | 
			
		||||
    arguments: ['@theme_handler', '@config.factory', '@config.installer', '@module_handler', '@config.manager', '@asset.css.collection_optimizer', '@router.builder', '@logger.channel.default', '@state']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,12 +10,12 @@ use Drupal\Component\Utility\Html;
 | 
			
		|||
use Drupal\Component\Render\FormattableMarkup;
 | 
			
		||||
use Drupal\Component\Utility\Unicode;
 | 
			
		||||
use Drupal\Core\Config\BootstrapConfigStorageFactory;
 | 
			
		||||
use Drupal\Core\Extension\Exception\UnknownExtensionException;
 | 
			
		||||
use Drupal\Core\Logger\RfcLogLevel;
 | 
			
		||||
use Drupal\Core\Test\TestDatabase;
 | 
			
		||||
use Drupal\Core\Session\AccountInterface;
 | 
			
		||||
use Drupal\Core\Utility\Error;
 | 
			
		||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Minimum supported version of PHP.
 | 
			
		||||
| 
						 | 
				
			
			@ -223,10 +223,6 @@ function config_get_config_directory($type) {
 | 
			
		|||
 *   The filename of the requested item or NULL if the item is not found.
 | 
			
		||||
 */
 | 
			
		||||
function drupal_get_filename($type, $name, $filename = NULL) {
 | 
			
		||||
  // The location of files will not change during the request, so do not use
 | 
			
		||||
  // drupal_static().
 | 
			
		||||
  static $files = [];
 | 
			
		||||
 | 
			
		||||
  // Type 'core' only exists to simplify application-level logic; it always maps
 | 
			
		||||
  // to the /core directory, whereas $name is ignored. It is only requested via
 | 
			
		||||
  // drupal_get_path(). /core/core.info.yml does not exist, but is required
 | 
			
		||||
| 
						 | 
				
			
			@ -235,45 +231,31 @@ function drupal_get_filename($type, $name, $filename = NULL) {
 | 
			
		|||
    return 'core/core.info.yml';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ($type === 'module' || $type === 'profile') {
 | 
			
		||||
    $service_id = 'extension.list.' . $type;
 | 
			
		||||
  try {
 | 
			
		||||
    /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
 | 
			
		||||
    $extension_list = \Drupal::service($service_id);
 | 
			
		||||
    $extension_list = \Drupal::service("extension.list.$type");
 | 
			
		||||
    if (isset($filename)) {
 | 
			
		||||
      // Manually add the info file path of an extension.
 | 
			
		||||
      $extension_list->setPathname($name, $filename);
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      return $extension_list->getPathname($name);
 | 
			
		||||
    }
 | 
			
		||||
    catch (UnknownExtensionException $e) {
 | 
			
		||||
      // Catch the exception. This will result in triggering an error.
 | 
			
		||||
    }
 | 
			
		||||
    return $extension_list->getPathname($name);
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
 | 
			
		||||
    if (!isset($files[$type])) {
 | 
			
		||||
      $files[$type] = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isset($filename)) {
 | 
			
		||||
      $files[$type][$name] = $filename;
 | 
			
		||||
    }
 | 
			
		||||
    elseif (!isset($files[$type][$name])) {
 | 
			
		||||
      // If still unknown, retrieve the file list prepared in state by
 | 
			
		||||
      // \Drupal\Core\Extension\ExtensionList() and
 | 
			
		||||
      // \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData().
 | 
			
		||||
      if (!isset($files[$type][$name]) && \Drupal::hasService('state')) {
 | 
			
		||||
        $files[$type] += \Drupal::state()->get('system.' . $type . '.files', []);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isset($files[$type][$name])) {
 | 
			
		||||
      return $files[$type][$name];
 | 
			
		||||
    }
 | 
			
		||||
  catch (ServiceNotFoundException $e) {
 | 
			
		||||
    // Catch the exception. This will result in triggering an error.
 | 
			
		||||
    // If the service is unknown, create a user-level error message.
 | 
			
		||||
    trigger_error(
 | 
			
		||||
      sprintf('Unknown type specified: "%s". Must be one of: "core", "profile", "module", "theme", or "theme_engine".', $type),
 | 
			
		||||
      E_USER_WARNING
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  catch (\InvalidArgumentException $e) {
 | 
			
		||||
    // Catch the exception. This will result in triggering an error.
 | 
			
		||||
    // If the filename is still unknown, create a user-level error message.
 | 
			
		||||
    trigger_error(
 | 
			
		||||
      sprintf('The following %s is missing from the file system: %s', $type, $name),
 | 
			
		||||
      E_USER_WARNING
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  // If the filename is still unknown, create a user-level error message.
 | 
			
		||||
  trigger_error(new FormattableMarkup('The following @type is missing from the file system: @name', ['@type' => $type, '@name' => $name]), E_USER_WARNING);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,41 +14,31 @@ use Drupal\Core\Extension\ExtensionDiscovery;
 | 
			
		|||
 *   The type of list to return:
 | 
			
		||||
 *   - theme: All installed themes.
 | 
			
		||||
 *
 | 
			
		||||
 * @return
 | 
			
		||||
 * @return array
 | 
			
		||||
 *   An associative array of themes, keyed by name.
 | 
			
		||||
 *   For $type 'theme', the array values are objects representing the
 | 
			
		||||
 *   respective database row, with the 'info' property already unserialized.
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use
 | 
			
		||||
 *   \Drupal::service('theme_handler')->listInfo() instead.
 | 
			
		||||
 *
 | 
			
		||||
 * @see https://www.drupal.org/node/2709919
 | 
			
		||||
 * @see \Drupal\Core\Extension\ThemeHandler::listInfo()
 | 
			
		||||
 */
 | 
			
		||||
function system_list($type) {
 | 
			
		||||
  $lists = &drupal_static(__FUNCTION__);
 | 
			
		||||
  if ($cached = \Drupal::cache('bootstrap')->get('system_list')) {
 | 
			
		||||
    $lists = $cached->data;
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    $lists = [
 | 
			
		||||
      'theme' => [],
 | 
			
		||||
      'filepaths' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    // ThemeHandler maintains the 'system.theme.data' state record.
 | 
			
		||||
    $theme_data = \Drupal::state()->get('system.theme.data', []);
 | 
			
		||||
    foreach ($theme_data as $name => $theme) {
 | 
			
		||||
      $lists['theme'][$name] = $theme;
 | 
			
		||||
      $lists['filepaths'][] = [
 | 
			
		||||
        'type' => 'theme',
 | 
			
		||||
        'name' => $name,
 | 
			
		||||
        'filepath' => $theme->getPathname(),
 | 
			
		||||
      ];
 | 
			
		||||
    }
 | 
			
		||||
    \Drupal::cache('bootstrap')->set('system_list', $lists);
 | 
			
		||||
  }
 | 
			
		||||
  // To avoid a separate database lookup for the filepath, prime the
 | 
			
		||||
  // drupal_get_filename() static cache with all enabled themes.
 | 
			
		||||
  foreach ($lists['filepaths'] as $item) {
 | 
			
		||||
    system_register($item['type'], $item['name'], $item['filepath']);
 | 
			
		||||
  }
 | 
			
		||||
  @trigger_error('system_list() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal::service(\'theme_handler\')->listInfo() instead. See https://www.drupal.org/node/2709919', E_USER_DEPRECATED);
 | 
			
		||||
 | 
			
		||||
  $lists = [
 | 
			
		||||
    'theme' => \Drupal::service('theme_handler')->listInfo(),
 | 
			
		||||
    'filepaths' => [],
 | 
			
		||||
  ];
 | 
			
		||||
  foreach ($lists['theme'] as $name => $theme) {
 | 
			
		||||
    $lists['filepaths'][] = [
 | 
			
		||||
      'type' => 'theme',
 | 
			
		||||
      'name' => $name,
 | 
			
		||||
      'filepath' => $theme->getPathname(),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
  return $lists[$type];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +46,10 @@ function system_list($type) {
 | 
			
		|||
 * Resets all system_list() caches.
 | 
			
		||||
 */
 | 
			
		||||
function system_list_reset() {
 | 
			
		||||
  drupal_static_reset('system_list');
 | 
			
		||||
  \Drupal::service('extension.list.profile')->reset();
 | 
			
		||||
  \Drupal::service('extension.list.module')->reset();
 | 
			
		||||
  \Drupal::cache('bootstrap')->delete('system_list');
 | 
			
		||||
  \Drupal::service('extension.list.theme_engine')->reset();
 | 
			
		||||
  \Drupal::service('extension.list.theme')->reset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ function theme_get_registry($complete = TRUE) {
 | 
			
		|||
/**
 | 
			
		||||
 * Returns an array of default theme features.
 | 
			
		||||
 *
 | 
			
		||||
 * @see \Drupal\Core\Extension\ThemeHandler::$defaultFeatures
 | 
			
		||||
 * @see \Drupal\Core\Extension\ThemeExtensionList::$defaults
 | 
			
		||||
 */
 | 
			
		||||
function _system_default_theme_features() {
 | 
			
		||||
  return [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ function _drupal_maintenance_theme() {
 | 
			
		|||
  $theme_init = \Drupal::service('theme.initialization');
 | 
			
		||||
  $theme_handler = \Drupal::service('theme_handler');
 | 
			
		||||
  if (empty($themes) || !isset($themes[$custom_theme])) {
 | 
			
		||||
    $themes = $theme_handler->rebuildThemeData();
 | 
			
		||||
    $themes = \Drupal::service('extension.list.theme')->getList();
 | 
			
		||||
    $theme_handler->addTheme($themes[$custom_theme]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -169,8 +169,8 @@ class Extension implements \Serializable {
 | 
			
		|||
      'filename' => $this->filename,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // @todo ThemeHandler::listInfo(), ThemeHandler::rebuildThemeData(), and
 | 
			
		||||
    //   system_list() are adding custom properties to the Extension object.
 | 
			
		||||
    // @todo \Drupal\Core\Extension\ThemeExtensionList is adding custom
 | 
			
		||||
    //   properties to the Extension object.
 | 
			
		||||
    $info = new \ReflectionObject($this);
 | 
			
		||||
    foreach ($info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
 | 
			
		||||
      $data[$property->getName()] = $property->getValue($this);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,12 @@ use Drupal\Core\State\StateInterface;
 | 
			
		|||
 * Provides available extensions.
 | 
			
		||||
 *
 | 
			
		||||
 * The extension list is per extension type, like module, theme and profile.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This class is not yet stable and therefore there are no guarantees that the
 | 
			
		||||
 *   internal implementations including constructor signature and protected
 | 
			
		||||
 *   properties / methods will not change over time. This will be reviewed after
 | 
			
		||||
 *   https://www.drupal.org/project/drupal/issues/2940481
 | 
			
		||||
 */
 | 
			
		||||
abstract class ExtensionList {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -305,15 +311,7 @@ abstract class ExtensionList {
 | 
			
		|||
 | 
			
		||||
    // Read info files for each extension.
 | 
			
		||||
    foreach ($extensions as $extension_name => $extension) {
 | 
			
		||||
      // Look for the info file.
 | 
			
		||||
      $extension->info = $this->infoParser->parse($extension->getPathname());
 | 
			
		||||
 | 
			
		||||
      // Add the info file modification time, so it becomes available for
 | 
			
		||||
      // contributed extensions to use for ordering extension lists.
 | 
			
		||||
      $extension->info['mtime'] = $extension->getMTime();
 | 
			
		||||
 | 
			
		||||
      // Merge extension type-specific defaults.
 | 
			
		||||
      $extension->info += $this->defaults;
 | 
			
		||||
      $extension->info = $this->createExtensionInfo($extension);
 | 
			
		||||
 | 
			
		||||
      // Invoke hook_system_info_alter() to give installed modules a chance to
 | 
			
		||||
      // modify the data in the .info.yml files if necessary.
 | 
			
		||||
| 
						 | 
				
			
			@ -541,4 +539,26 @@ abstract class ExtensionList {
 | 
			
		|||
    return dirname($this->getPathname($extension_name));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates the info value for an extension object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Extension\Extension $extension
 | 
			
		||||
   *   The extension whose info is to be altered.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   *   The extension info array.
 | 
			
		||||
   */
 | 
			
		||||
  protected function createExtensionInfo(Extension $extension) {
 | 
			
		||||
    $info = $this->infoParser->parse($extension->getPathname());
 | 
			
		||||
 | 
			
		||||
    // Add the info file modification time, so it becomes available for
 | 
			
		||||
    // contributed extensions to use for ordering extension lists.
 | 
			
		||||
    $info['mtime'] = $extension->getMTime();
 | 
			
		||||
 | 
			
		||||
    // Merge extension type-specific defaults.
 | 
			
		||||
    $info += $this->defaults;
 | 
			
		||||
 | 
			
		||||
    return $info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,12 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a list of available modules.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This class is not yet stable and therefore there are no guarantees that the
 | 
			
		||||
 *   internal implementations including constructor signature and protected
 | 
			
		||||
 *   properties / methods will not change over time. This will be reviewed after
 | 
			
		||||
 *   https://www.drupal.org/project/drupal/issues/2940481
 | 
			
		||||
 */
 | 
			
		||||
class ModuleExtensionList extends ExtensionList {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,12 @@ namespace Drupal\Core\Extension;
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a list of installation profiles.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This class is not yet stable and therefore there are no guarantees that the
 | 
			
		||||
 *   internal implementations including constructor signature and protected
 | 
			
		||||
 *   properties / methods will not change over time. This will be reviewed after
 | 
			
		||||
 *   https://www.drupal.org/project/drupal/issues/2940481
 | 
			
		||||
 */
 | 
			
		||||
class ProfileExtensionList extends ExtensionList {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Extension;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a list of available theme engines.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This class is not yet stable and therefore there are no guarantees that the
 | 
			
		||||
 *   internal implementations including constructor signature and protected
 | 
			
		||||
 *   properties / methods will not change over time. This will be reviewed after
 | 
			
		||||
 *   https://www.drupal.org/project/drupal/issues/2940481
 | 
			
		||||
 */
 | 
			
		||||
class ThemeEngineExtensionList extends ExtensionList {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $defaults = [
 | 
			
		||||
    'dependencies' => [],
 | 
			
		||||
    'description' => '',
 | 
			
		||||
    'package' => 'Other',
 | 
			
		||||
    'version' => NULL,
 | 
			
		||||
    'php' => DRUPAL_MINIMUM_PHP,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getInstalledExtensionNames() {
 | 
			
		||||
    // Theme engines do not have an 'install' state, so return names of all
 | 
			
		||||
    // discovered theme engines.
 | 
			
		||||
    return array_keys($this->extensions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,289 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\CacheBackendInterface;
 | 
			
		||||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\State\StateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides a list of available themes.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *   This class is not yet stable and therefore there are no guarantees that the
 | 
			
		||||
 *   internal implementations including constructor signature and protected
 | 
			
		||||
 *   properties / methods will not change over time. This will be reviewed after
 | 
			
		||||
 *   https://www.drupal.org/project/drupal/issues/2940481
 | 
			
		||||
 */
 | 
			
		||||
class ThemeExtensionList extends ExtensionList {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected $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' => '',
 | 
			
		||||
    // The following array should be kept inline with
 | 
			
		||||
    // _system_default_theme_features().
 | 
			
		||||
    'features' => [
 | 
			
		||||
      'favicon',
 | 
			
		||||
      'logo',
 | 
			
		||||
      'node_user_picture',
 | 
			
		||||
      'comment_user_picture',
 | 
			
		||||
      'comment_user_verification',
 | 
			
		||||
    ],
 | 
			
		||||
    'screenshot' => 'screenshot.png',
 | 
			
		||||
    'php' => DRUPAL_MINIMUM_PHP,
 | 
			
		||||
    'libraries' => [],
 | 
			
		||||
    'libraries_extend' => [],
 | 
			
		||||
    'libraries_override' => [],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The config factory.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ConfigFactoryInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The theme engine list needed by this theme list.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ThemeEngineExtensionList
 | 
			
		||||
   */
 | 
			
		||||
  protected $engineList;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The list of installed themes.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected $installedThemes;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Constructs a new ThemeExtensionList instance.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $root
 | 
			
		||||
   *   The app root.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The extension type.
 | 
			
		||||
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
 | 
			
		||||
   *   The cache.
 | 
			
		||||
   * @param \Drupal\Core\Extension\InfoParserInterface $info_parser
 | 
			
		||||
   *   The info parser.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
 | 
			
		||||
   *   The module handler.
 | 
			
		||||
   * @param \Drupal\Core\State\StateInterface $state
 | 
			
		||||
   *   The state service.
 | 
			
		||||
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
 | 
			
		||||
   *   The config factory.
 | 
			
		||||
   * @param \Drupal\Core\Extension\ThemeEngineExtensionList $engine_list
 | 
			
		||||
   *   The theme engine extension listing.
 | 
			
		||||
   * @param string $install_profile
 | 
			
		||||
   *   The install profile used by the site.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($root, $type, CacheBackendInterface $cache, InfoParserInterface $info_parser, ModuleHandlerInterface $module_handler, StateInterface $state, ConfigFactoryInterface $config_factory, ThemeEngineExtensionList $engine_list, $install_profile) {
 | 
			
		||||
    parent::__construct($root, $type, $cache, $info_parser, $module_handler, $state, $install_profile);
 | 
			
		||||
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->engineList = $engine_list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function doList() {
 | 
			
		||||
    // Find themes.
 | 
			
		||||
    $themes = parent::doList();
 | 
			
		||||
 | 
			
		||||
    $engines = $this->engineList->getList();
 | 
			
		||||
    // Always get the freshest list of themes (rather than the already cached
 | 
			
		||||
    // list in $this->installedThemes) when building the theme listing because a
 | 
			
		||||
    // theme could have just been installed or uninstalled.
 | 
			
		||||
    $this->installedThemes = $this->configFactory->get('core.extension')->get('theme') ?: [];
 | 
			
		||||
 | 
			
		||||
    $sub_themes = [];
 | 
			
		||||
    // Read info files for each theme.
 | 
			
		||||
    foreach ($themes as $name => $theme) {
 | 
			
		||||
      // Defaults to 'twig' (see self::defaults above).
 | 
			
		||||
      $engine = $theme->info['engine'];
 | 
			
		||||
      if (isset($engines[$engine])) {
 | 
			
		||||
        $theme->owner = $engines[$engine]->getExtensionPathname();
 | 
			
		||||
        $theme->prefix = $engines[$engine]->getName();
 | 
			
		||||
      }
 | 
			
		||||
      // Add this theme as a sub-theme if it has a base theme.
 | 
			
		||||
      if (!empty($theme->info['base theme'])) {
 | 
			
		||||
        $sub_themes[] = $name;
 | 
			
		||||
      }
 | 
			
		||||
      // Add weight and status.
 | 
			
		||||
      $theme->status = (int) isset($this->installedThemes[$name]);
 | 
			
		||||
      $theme->weight = isset($this->installedThemes[$name]) ? $this->installedThemes[$name] : 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Build dependencies.
 | 
			
		||||
    $themes = $this->moduleHandler->buildModuleDependencies($themes);
 | 
			
		||||
 | 
			
		||||
    // After establishing the full list of available themes, fill in data for
 | 
			
		||||
    // sub-themes.
 | 
			
		||||
    $this->fillInSubThemeData($themes, $sub_themes);
 | 
			
		||||
 | 
			
		||||
    return $themes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fills in data for themes that are also sub-themes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $themes
 | 
			
		||||
   *   The array of partly processed theme information.
 | 
			
		||||
   * @param array $sub_themes
 | 
			
		||||
   *   A list of themes from the $theme array that are also sub-themes.
 | 
			
		||||
   */
 | 
			
		||||
  protected function fillInSubThemeData(array &$themes, array $sub_themes) {
 | 
			
		||||
    foreach ($sub_themes as $name) {
 | 
			
		||||
      $sub_theme = $themes[$name];
 | 
			
		||||
      // The $base_themes property is optional; only set for sub themes.
 | 
			
		||||
      // @see ThemeHandlerInterface::listInfo()
 | 
			
		||||
      $sub_theme->base_themes = $this->doGetBaseThemes($themes, $name);
 | 
			
		||||
      // empty() cannot be used here, since static::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[$name] = $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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Finds all the base themes for the specified theme.
 | 
			
		||||
   *
 | 
			
		||||
   * Themes can inherit templates and function implementations from earlier
 | 
			
		||||
   * themes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Extension\Extension[] $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) {
 | 
			
		||||
    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, array $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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function createExtensionInfo(Extension $extension) {
 | 
			
		||||
    $info = parent::createExtensionInfo($extension);
 | 
			
		||||
    // Remove the default Stable base theme when 'base theme: false' is set in
 | 
			
		||||
    // a theme .info.yml file.
 | 
			
		||||
    if ($info['base theme'] === FALSE) {
 | 
			
		||||
      unset($info['base theme']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!empty($info['base theme'])) {
 | 
			
		||||
      // Add the base theme as a proper dependency.
 | 
			
		||||
      $info['dependencies'][] = $info['base theme'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prefix screenshot with theme path.
 | 
			
		||||
    if (!empty($info['screenshot'])) {
 | 
			
		||||
      $info['screenshot'] = $extension->getPath() . '/' . $info['screenshot'];
 | 
			
		||||
    }
 | 
			
		||||
    return $info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function getInstalledExtensionNames() {
 | 
			
		||||
    // Cache the installed themes to avoid multiple calls to the config system.
 | 
			
		||||
    if (!isset($this->installedThemes)) {
 | 
			
		||||
      $this->installedThemes = $this->configFactory->get('core.extension')->get('theme') ?: [];
 | 
			
		||||
    }
 | 
			
		||||
    return array_keys($this->installedThemes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function reset() {
 | 
			
		||||
    parent::reset();
 | 
			
		||||
    $this->installedThemes = NULL;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,28 +5,12 @@ namespace Drupal\Core\Extension;
 | 
			
		|||
use Drupal\Core\Config\ConfigFactoryInterface;
 | 
			
		||||
use Drupal\Core\Extension\Exception\UninstalledExtensionException;
 | 
			
		||||
use Drupal\Core\Extension\Exception\UnknownExtensionException;
 | 
			
		||||
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.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			@ -41,68 +25,12 @@ class ThemeHandler implements ThemeHandlerInterface {
 | 
			
		|||
   */
 | 
			
		||||
  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
 | 
			
		||||
   * @var \Drupal\Core\Extension\ThemeExtensionList
 | 
			
		||||
   */
 | 
			
		||||
  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;
 | 
			
		||||
  protected $themeList;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The app root.
 | 
			
		||||
| 
						 | 
				
			
			@ -118,22 +46,13 @@ class ThemeHandler implements ThemeHandlerInterface {
 | 
			
		|||
   *   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).
 | 
			
		||||
   * @param \Drupal\Core\Extension\ThemeExtensionList $theme_list
 | 
			
		||||
   *   A extension discovery instance.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct($root, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, StateInterface $state, InfoParserInterface $info_parser, ExtensionDiscovery $extension_discovery = NULL) {
 | 
			
		||||
  public function __construct($root, ConfigFactoryInterface $config_factory, ThemeExtensionList $theme_list) {
 | 
			
		||||
    $this->root = $root;
 | 
			
		||||
    $this->configFactory = $config_factory;
 | 
			
		||||
    $this->moduleHandler = $module_handler;
 | 
			
		||||
    $this->state = $state;
 | 
			
		||||
    $this->infoParser = $info_parser;
 | 
			
		||||
    $this->extensionDiscovery = $extension_discovery;
 | 
			
		||||
    $this->themeList = $theme_list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			@ -181,16 +100,10 @@ class ThemeHandler implements ThemeHandlerInterface {
 | 
			
		|||
  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);
 | 
			
		||||
      $installed_themes = $this->configFactory->get('core.extension')->get('theme');
 | 
			
		||||
      if (!empty($installed_themes)) {
 | 
			
		||||
        $installed_themes = array_intersect_key($this->themeList->getList(), $installed_themes);
 | 
			
		||||
        array_map([$this, 'addTheme'], $installed_themes);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->list;
 | 
			
		||||
| 
						 | 
				
			
			@ -200,6 +113,11 @@ class ThemeHandler implements ThemeHandlerInterface {
 | 
			
		|||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function addTheme(Extension $theme) {
 | 
			
		||||
    // Register the namespaces of installed themes.
 | 
			
		||||
    // @todo Implement proper theme registration
 | 
			
		||||
    // https://www.drupal.org/project/drupal/issues/2941757
 | 
			
		||||
    \Drupal::service('class_loader')->addPsr4('Drupal\\' . $theme->getName() . '\\', $this->root . '/' . $theme->getPath() . '/src');
 | 
			
		||||
 | 
			
		||||
    if (!empty($theme->info['libraries'])) {
 | 
			
		||||
      foreach ($theme->info['libraries'] as $library => $name) {
 | 
			
		||||
        $theme->libraries[$library] = $name;
 | 
			
		||||
| 
						 | 
				
			
			@ -218,32 +136,21 @@ class ThemeHandler implements ThemeHandlerInterface {
 | 
			
		|||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function refreshInfo() {
 | 
			
		||||
    $extension_config = $this->configFactory->get('core.extension');
 | 
			
		||||
    $installed = $extension_config->get('theme');
 | 
			
		||||
    $installed = $this->configFactory->get('core.extension')->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->themeList->reset();
 | 
			
		||||
    $this->list = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -251,214 +158,21 @@ class ThemeHandler implements ThemeHandlerInterface {
 | 
			
		|||
   * {@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;
 | 
			
		||||
    return $this->themeList->reset()->getList();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@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;
 | 
			
		||||
    return $this->themeList->getBaseThemes($themes, $theme);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getName($theme) {
 | 
			
		||||
    $themes = $this->listInfo();
 | 
			
		||||
    if (!isset($themes[$theme])) {
 | 
			
		||||
      throw new UnknownExtensionException("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');
 | 
			
		||||
    return $this->themeList->getName($theme);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,8 +56,8 @@ interface ThemeHandlerInterface {
 | 
			
		|||
   *
 | 
			
		||||
   * @return \Drupal\Core\Extension\Extension[]
 | 
			
		||||
   *   An associative array of the currently installed themes. The keys are the
 | 
			
		||||
   *   themes' machine names and the values are objects having the following
 | 
			
		||||
   *   properties:
 | 
			
		||||
   *   themes' machine names and the values are Extension 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 installed, 0 for uninstalled themes.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -174,22 +174,12 @@ class ThemeInstaller implements ThemeInstallerInterface {
 | 
			
		|||
        ->set("theme.$key", 0)
 | 
			
		||||
        ->save(TRUE);
 | 
			
		||||
 | 
			
		||||
      // Add the theme to the current list.
 | 
			
		||||
      // @todo Remove all code that relies on $status property.
 | 
			
		||||
      $theme_data[$key]->status = 1;
 | 
			
		||||
      $this->themeHandler->addTheme($theme_data[$key]);
 | 
			
		||||
 | 
			
		||||
      // Update the current theme data accordingly.
 | 
			
		||||
      $current_theme_data = $this->state->get('system.theme.data', []);
 | 
			
		||||
      $current_theme_data[$key] = $theme_data[$key];
 | 
			
		||||
      $this->state->set('system.theme.data', $current_theme_data);
 | 
			
		||||
 | 
			
		||||
      // Reset theme settings.
 | 
			
		||||
      $theme_settings = &drupal_static('theme_get_setting');
 | 
			
		||||
      unset($theme_settings[$key]);
 | 
			
		||||
 | 
			
		||||
      // @todo Remove system_list().
 | 
			
		||||
      $this->systemListReset();
 | 
			
		||||
      // Reset theme listing.
 | 
			
		||||
      $this->themeHandler->reset();
 | 
			
		||||
 | 
			
		||||
      // Only install default configuration if this theme has not been installed
 | 
			
		||||
      // already.
 | 
			
		||||
| 
						 | 
				
			
			@ -245,14 +235,10 @@ class ThemeInstaller implements ThemeInstallerInterface {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    $this->cssCollectionOptimizer->deleteAll();
 | 
			
		||||
    $current_theme_data = $this->state->get('system.theme.data', []);
 | 
			
		||||
    foreach ($theme_list as $key) {
 | 
			
		||||
      // The value is not used; the weight is ignored for themes currently.
 | 
			
		||||
      $extension_config->clear("theme.$key");
 | 
			
		||||
 | 
			
		||||
      // Update the current theme data accordingly.
 | 
			
		||||
      unset($current_theme_data[$key]);
 | 
			
		||||
 | 
			
		||||
      // Reset theme settings.
 | 
			
		||||
      $theme_settings = &drupal_static('theme_get_setting');
 | 
			
		||||
      unset($theme_settings[$key]);
 | 
			
		||||
| 
						 | 
				
			
			@ -264,11 +250,10 @@ class ThemeInstaller implements ThemeInstallerInterface {
 | 
			
		|||
    // Don't check schema when uninstalling a theme since we are only clearing
 | 
			
		||||
    // keys.
 | 
			
		||||
    $extension_config->save(TRUE);
 | 
			
		||||
    $this->state->set('system.theme.data', $current_theme_data);
 | 
			
		||||
 | 
			
		||||
    // @todo Remove system_list().
 | 
			
		||||
    $this->themeHandler->refreshInfo();
 | 
			
		||||
    // Refresh theme info.
 | 
			
		||||
    $this->resetSystem();
 | 
			
		||||
    $this->themeHandler->reset();
 | 
			
		||||
 | 
			
		||||
    $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -280,7 +265,6 @@ class ThemeInstaller implements ThemeInstallerInterface {
 | 
			
		|||
    if ($this->routeBuilder) {
 | 
			
		||||
      $this->routeBuilder->setRebuildNeeded();
 | 
			
		||||
    }
 | 
			
		||||
    $this->systemListReset();
 | 
			
		||||
 | 
			
		||||
    // @todo It feels wrong to have the requirement to clear the local tasks
 | 
			
		||||
    //   cache here.
 | 
			
		||||
| 
						 | 
				
			
			@ -295,11 +279,4 @@ class ThemeInstaller implements ThemeInstallerInterface {
 | 
			
		|||
    drupal_theme_rebuild();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wraps system_list_reset().
 | 
			
		||||
   */
 | 
			
		||||
  protected function systemListReset() {
 | 
			
		||||
    system_list_reset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -129,10 +129,9 @@ function hook_module_implements_alter(&$implementations, $hook) {
 | 
			
		|||
/**
 | 
			
		||||
 * Alter the information parsed from module and theme .info.yml files.
 | 
			
		||||
 *
 | 
			
		||||
 * This hook is invoked in _system_rebuild_module_data() and in
 | 
			
		||||
 * \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData(). A module
 | 
			
		||||
 * may implement this hook in order to add to or alter the data generated by
 | 
			
		||||
 * reading the .info.yml file with \Drupal\Core\Extension\InfoParser.
 | 
			
		||||
 * This hook is invoked in \Drupal\Core\Extension\ExtensionList::doList(). A
 | 
			
		||||
 * module may implement this hook in order to add to or alter the data generated
 | 
			
		||||
 * by reading the .info.yml file with \Drupal\Core\Extension\InfoParser.
 | 
			
		||||
 *
 | 
			
		||||
 * Using implementations of this hook to make modules required by setting the
 | 
			
		||||
 * $info['required'] key is discouraged. Doing so will slow down the module
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Installer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides common functionality for the extension list classes.
 | 
			
		||||
 */
 | 
			
		||||
trait ExtensionListTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Static version of the added file names during the installer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected static $staticAddedPathNames;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Extension\ExtensionList::setPathname()
 | 
			
		||||
   */
 | 
			
		||||
  public function setPathname($extension_name, $pathname) {
 | 
			
		||||
    parent::setPathname($extension_name, $pathname);
 | 
			
		||||
 | 
			
		||||
    // In the early installer the container is rebuilt multiple times. Therefore
 | 
			
		||||
    // we have to keep the added filenames across those rebuilds. This is not a
 | 
			
		||||
    // final design, but rather just a workaround resolved at some point,
 | 
			
		||||
    // hopefully.
 | 
			
		||||
    // @todo Remove as part of https://drupal.org/project/drupal/issues/2934063
 | 
			
		||||
    static::$staticAddedPathNames[$extension_name] = $pathname;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @see \Drupal\Core\Extension\ExtensionList::getPathname()
 | 
			
		||||
   */
 | 
			
		||||
  public function getPathname($extension_name) {
 | 
			
		||||
    if (isset($this->addedPathNames[$extension_name])) {
 | 
			
		||||
      return $this->addedPathNames[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    elseif (isset($this->pathNames[$extension_name])) {
 | 
			
		||||
      return $this->pathNames[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    elseif (isset(static::$staticAddedPathNames[$extension_name])) {
 | 
			
		||||
      return static::$staticAddedPathNames[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    elseif (($path_names = $this->getPathnames()) && isset($path_names[$extension_name])) {
 | 
			
		||||
      // Ensure we don't have to do path scanning more than really needed.
 | 
			
		||||
      foreach ($path_names as $extension => $path_name) {
 | 
			
		||||
        static::$staticAddedPathNames[$extension] = $path_name;
 | 
			
		||||
      }
 | 
			
		||||
      return $path_names[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,51 +8,6 @@ use Drupal\Core\Extension\ModuleExtensionList;
 | 
			
		|||
 * Overrides the module extension list to have a static cache.
 | 
			
		||||
 */
 | 
			
		||||
class InstallerModuleExtensionList extends ModuleExtensionList {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Static version of the added file names during the installer.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected static $staticAddedPathNames;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function setPathname($extension_name, $pathname) {
 | 
			
		||||
    parent::setPathname($extension_name, $pathname);
 | 
			
		||||
 | 
			
		||||
    // In the early installer the container is rebuilt multiple times. Therefore
 | 
			
		||||
    // we have to keep the added filenames across those rebuilds. This is not a
 | 
			
		||||
    // final design, but rather just a workaround resolved at some point,
 | 
			
		||||
    // hopefully.
 | 
			
		||||
    // @todo Remove as part of https://drupal.org/project/drupal/issues/2934063
 | 
			
		||||
    static::$staticAddedPathNames[$extension_name] = $pathname;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getPathname($extension_name) {
 | 
			
		||||
    if (isset($this->addedPathNames[$extension_name])) {
 | 
			
		||||
      return $this->addedPathNames[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    elseif (isset($this->pathNames[$extension_name])) {
 | 
			
		||||
      return $this->pathNames[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    elseif (isset(static::$staticAddedPathNames[$extension_name])) {
 | 
			
		||||
      return static::$staticAddedPathNames[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    elseif (($path_names = $this->getPathnames()) && isset($path_names[$extension_name])) {
 | 
			
		||||
      // Ensure we don't have to do path scanning more than really needed.
 | 
			
		||||
      foreach ($path_names as $extension => $path_name) {
 | 
			
		||||
        static::$staticAddedPathNames[$extension] = $path_name;
 | 
			
		||||
      }
 | 
			
		||||
      return $path_names[$extension_name];
 | 
			
		||||
    }
 | 
			
		||||
    throw new \InvalidArgumentException("The {$this->type} $extension_name does not exist.");
 | 
			
		||||
  }
 | 
			
		||||
  use ExtensionListTrait;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Installer;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ThemeEngineExtensionList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Overrides the theme engine extension list to have a static cache.
 | 
			
		||||
 */
 | 
			
		||||
class InstallerThemeEngineExtensionList extends ThemeEngineExtensionList {
 | 
			
		||||
  use ExtensionListTrait;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Core\Installer;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Extension\ThemeExtensionList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Overrides the theme extension list to have a static cache.
 | 
			
		||||
 */
 | 
			
		||||
class InstallerThemeExtensionList extends ThemeExtensionList {
 | 
			
		||||
  use ExtensionListTrait;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,8 @@ class NormalInstallerServiceProvider implements ServiceProviderInterface {
 | 
			
		|||
  public function register(ContainerBuilder $container) {
 | 
			
		||||
    // Use a performance optimised module extension list.
 | 
			
		||||
    $container->getDefinition('extension.list.module')->setClass('Drupal\Core\Installer\InstallerModuleExtensionList');
 | 
			
		||||
    $container->getDefinition('extension.list.theme')->setClass('Drupal\Core\Installer\InstallerThemeExtensionList');
 | 
			
		||||
    $container->getDefinition('extension.list.theme_engine')->setClass('Drupal\Core\Installer\InstallerThemeEngineExtensionList');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,7 +161,7 @@ class ActiveTheme {
 | 
			
		|||
  /**
 | 
			
		||||
   * Returns the path to the theme engine for root themes.
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData
 | 
			
		||||
   * @see \Drupal\Core\Extension\ThemeExtensionList::doList()
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed
 | 
			
		||||
   */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1829,7 +1829,8 @@ function system_update_8014() {
 | 
			
		|||
  $theme_handler->refreshInfo();
 | 
			
		||||
  foreach ($theme_handler->listInfo() as $theme) {
 | 
			
		||||
    // We first check that a base theme is set because if it's set to false then
 | 
			
		||||
    // it's unset in \Drupal\Core\Extension\ThemeHandler::rebuildThemeData().
 | 
			
		||||
    // it's unset in
 | 
			
		||||
    // \Drupal\Core\Extension\ThemeExtensionList::createExtensionInfo().
 | 
			
		||||
    if (isset($theme->info['base theme']) && $theme->info['base theme'] == 'stable') {
 | 
			
		||||
      $theme_handler->install(['stable']);
 | 
			
		||||
      return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ use Drupal\Component\Render\PlainTextOutput;
 | 
			
		|||
use Drupal\Component\Utility\UrlHelper;
 | 
			
		||||
use Drupal\Core\Asset\AttachedAssetsInterface;
 | 
			
		||||
use Drupal\Core\Cache\Cache;
 | 
			
		||||
use Drupal\Core\Extension\Exception\UnknownExtensionException;
 | 
			
		||||
use Drupal\Core\Queue\QueueGarbageCollectionInterface;
 | 
			
		||||
use Drupal\Core\Database\Query\AlterableInterface;
 | 
			
		||||
use Drupal\Core\Extension\Extension;
 | 
			
		||||
| 
						 | 
				
			
			@ -963,38 +962,20 @@ function system_check_directory($form_element, FormStateInterface $form_state) {
 | 
			
		|||
 *   array is returned.
 | 
			
		||||
 *
 | 
			
		||||
 * @see system_rebuild_module_data()
 | 
			
		||||
 * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
 | 
			
		||||
 * @see \Drupal\Core\Extension\ThemeExtensionList
 | 
			
		||||
 */
 | 
			
		||||
function system_get_info($type, $name = NULL) {
 | 
			
		||||
  if ($type == 'module') {
 | 
			
		||||
    /** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
 | 
			
		||||
    $module_list = \Drupal::service('extension.list.module');
 | 
			
		||||
    if (isset($name)) {
 | 
			
		||||
      try {
 | 
			
		||||
        return $module_list->getExtensionInfo($name);
 | 
			
		||||
      }
 | 
			
		||||
      catch (UnknownExtensionException $e) {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
  /** @var \Drupal\Core\Extension\ExtensionList $extension_list */
 | 
			
		||||
  $extension_list = \Drupal::service('extension.list.' . $type);
 | 
			
		||||
  if (isset($name)) {
 | 
			
		||||
    try {
 | 
			
		||||
      return $extension_list->getExtensionInfo($name);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      return $module_list->getAllInstalledInfo();
 | 
			
		||||
    catch (\InvalidArgumentException $e) {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    // @todo move into ThemeExtensionList https://www.drupal.org/node/2659940
 | 
			
		||||
    $info = [];
 | 
			
		||||
    $list = system_list($type);
 | 
			
		||||
    foreach ($list as $shortname => $item) {
 | 
			
		||||
      if (!empty($item->status)) {
 | 
			
		||||
        $info[$shortname] = $item->info;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (isset($name)) {
 | 
			
		||||
      return isset($info[$name]) ? $info[$name] : [];
 | 
			
		||||
    }
 | 
			
		||||
    return $info;
 | 
			
		||||
  }
 | 
			
		||||
  return $extension_list->getAllInstalledInfo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -158,4 +158,14 @@ class ThemeTestController extends ControllerBase {
 | 
			
		|||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Controller for testing a namespaced class in a theme.
 | 
			
		||||
   */
 | 
			
		||||
  public function testThemeClass() {
 | 
			
		||||
    return [
 | 
			
		||||
      '#theme' => 'theme_test_theme_class',
 | 
			
		||||
      '#title' => 'Testing loading a class from a .theme file',
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<p>{{ message }}</p>
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +70,11 @@ function theme_test_theme($existing, $type, $theme, $path) {
 | 
			
		|||
    'render element' => 'content',
 | 
			
		||||
    'base hook' => 'container',
 | 
			
		||||
  ];
 | 
			
		||||
  $items['theme_test_theme_class'] = [
 | 
			
		||||
    'variables' => [
 | 
			
		||||
      'message' => '',
 | 
			
		||||
    ],
 | 
			
		||||
  ];
 | 
			
		||||
  return $items;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,3 +110,10 @@ theme_test.preprocess_suggestions:
 | 
			
		|||
    _controller: '\Drupal\theme_test\ThemeTestController::preprocessSuggestions'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
 | 
			
		||||
theme_test.test_theme_class:
 | 
			
		||||
  path: '/theme-test/test-theme-class'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\theme_test\ThemeTestController::testThemeClass'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _access: 'TRUE'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,6 +80,20 @@ class ThemeTest extends BrowserTestBase {
 | 
			
		|||
    $this->assertTrue(in_array('page__front', $suggestions), 'Front page template was suggested.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests theme can provide classes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testClassLoading() {
 | 
			
		||||
    // Install test theme and set it as default.
 | 
			
		||||
    $this->config('system.theme')
 | 
			
		||||
      ->set('default', 'test_theme')
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->resetAll();
 | 
			
		||||
    // Visit page controller and confirm that the theme class is loaded.
 | 
			
		||||
    $this->drupalGet('/theme-test/test-theme-class');
 | 
			
		||||
    $this->assertText('Loading ThemeClass was successful.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures a theme's .info.yml file is able to override a module CSS file from being added to the page.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@ namespace Drupal\Tests\system\Kernel\Theme;
 | 
			
		|||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Drupal\Component\Render\MarkupInterface;
 | 
			
		||||
use Drupal\test_theme\ThemeClass;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests low-level theme functions.
 | 
			
		||||
| 
						 | 
				
			
			@ -146,13 +145,6 @@ class ThemeTest extends KernelTestBase {
 | 
			
		|||
    $this->assertThemeOutput('theme_test_render_element_children', $element, 'Foo', 'drupal_render() avoids #theme_wrappers recursion loop when rendering a render element.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests theme can provide classes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testClassLoading() {
 | 
			
		||||
    new ThemeClass();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests drupal_find_theme_templates().
 | 
			
		||||
   */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -154,3 +154,15 @@ function test_theme_preprocess_theme_test_preprocess_suggestions__kitten__flamin
 | 
			
		|||
function test_theme_preprocess_theme_test_preprocess_suggestions__kitten__meerkat__tarsier__moose(&$variables) {
 | 
			
		||||
  $variables['bar'] = 'Moose';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that a class can be loaded within a .theme file.
 | 
			
		||||
 */
 | 
			
		||||
function test_theme_preprocess_theme_test_theme_class(&$variables) {
 | 
			
		||||
  if (class_exists('\Drupal\test_theme\ThemeClass')) {
 | 
			
		||||
    $variables['message'] = 'Loading ThemeClass was successful.';
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    $variables['message'] = 'Loading ThemeClass failed.';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Extension\ThemeEngineExtensionList
 | 
			
		||||
 * @group Extension
 | 
			
		||||
 */
 | 
			
		||||
class ThemeEngineExtensionListTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getList
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetlist() {
 | 
			
		||||
    $settings = Settings::getAll();
 | 
			
		||||
    $settings['install_profile'] = 'testing';
 | 
			
		||||
    new Settings($settings);
 | 
			
		||||
 | 
			
		||||
    // Confirm that all theme engines are available.
 | 
			
		||||
    $theme_engines = \Drupal::service('extension.list.theme_engine')->getList();
 | 
			
		||||
    $this->assertArrayHasKey('twig', $theme_engines);
 | 
			
		||||
    $this->assertArrayHasKey('nyan_cat', $theme_engines);
 | 
			
		||||
    $this->assertCount(2, $theme_engines);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Site\Settings;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Extension\ThemeExtensionList
 | 
			
		||||
 * @group Extension
 | 
			
		||||
 */
 | 
			
		||||
class ThemeExtensionListTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::getList
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetlist() {
 | 
			
		||||
    $settings = Settings::getAll();
 | 
			
		||||
    $settings['install_profile'] = 'testing';
 | 
			
		||||
    new Settings($settings);
 | 
			
		||||
 | 
			
		||||
    \Drupal::configFactory()->getEditable('core.extension')
 | 
			
		||||
      ->set('module.testing', 1000)
 | 
			
		||||
      ->set('theme.test_theme', 0)
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    // The installation profile is provided by a container parameter.
 | 
			
		||||
    // Saving the configuration doesn't automatically trigger invalidation
 | 
			
		||||
    $this->container->get('kernel')->rebuildContainer();
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Extension\ThemeExtensionList $theme_extension_list */
 | 
			
		||||
    $theme_extension_list = \Drupal::service('extension.list.theme');
 | 
			
		||||
    $extensions = $theme_extension_list->getList();
 | 
			
		||||
 | 
			
		||||
    $this->assertArrayHasKey('test_theme', $extensions);
 | 
			
		||||
    $this->assertEquals(0, $extensions['test_theme']->weight);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Core\Theme;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Drupal\Core\Routing\NullMatcherDumper;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests system_list() function.
 | 
			
		||||
 *
 | 
			
		||||
 * In Drupal 8 the system_list() function only lists themes.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Extension
 | 
			
		||||
 * @group legacy
 | 
			
		||||
 */
 | 
			
		||||
class SystemListTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static $modules = ['system'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function register(ContainerBuilder $container) {
 | 
			
		||||
    parent::register($container);
 | 
			
		||||
    // Some test methods involve ModuleHandler operations, which attempt to
 | 
			
		||||
    // rebuild and dump routes.
 | 
			
		||||
    $container->register('router.dumper', NullMatcherDumper::class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp() {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests installing a theme.
 | 
			
		||||
   *
 | 
			
		||||
   * @expectedDeprecation system_list() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. Use \Drupal::service('theme_handler')->listInfo() instead. See https://www.drupal.org/node/2709919
 | 
			
		||||
   */
 | 
			
		||||
  public function testSystemList() {
 | 
			
		||||
    // Verifies that no themes are listed by default.
 | 
			
		||||
    $this->assertEmpty(system_list('theme'));
 | 
			
		||||
    $this->assertEmpty(system_list('filepaths'));
 | 
			
		||||
 | 
			
		||||
    $this->container->get('theme_installer')->install(['test_basetheme']);
 | 
			
		||||
 | 
			
		||||
    $themes = system_list('theme');
 | 
			
		||||
    $this->assertTrue(isset($themes['test_basetheme']));
 | 
			
		||||
    $this->assertEqual($themes['test_basetheme']->getName(), 'test_basetheme');
 | 
			
		||||
    $filepaths = system_list('filepaths');
 | 
			
		||||
    $this->assertEquals('test_basetheme', $filepaths[0]['name']);
 | 
			
		||||
    $this->assertEquals('core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml', $filepaths[0]['filepath']);
 | 
			
		||||
    $this->assertCount(1, $filepaths);
 | 
			
		||||
 | 
			
		||||
    $this->container->get('theme_installer')->uninstall(['test_basetheme']);
 | 
			
		||||
 | 
			
		||||
    $this->assertEmpty(system_list('theme'));
 | 
			
		||||
    $this->assertEmpty(system_list('filepaths'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +44,7 @@ class ThemeInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->assertFalse($this->extensionConfig()->get('theme'));
 | 
			
		||||
 | 
			
		||||
    $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
 | 
			
		||||
    $this->assertFalse(array_keys(system_list('theme')));
 | 
			
		||||
    $this->assertFalse(array_keys(\Drupal::service('theme_handler')->listInfo()));
 | 
			
		||||
 | 
			
		||||
    // Rebuilding available themes should always yield results though.
 | 
			
		||||
    $this->assertTrue($this->themeHandler()->rebuildThemeData()['stark'], 'ThemeHandler::rebuildThemeData() yields all available themes.');
 | 
			
		||||
| 
						 | 
				
			
			@ -70,8 +70,6 @@ class ThemeInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->assertTrue(isset($themes[$name]));
 | 
			
		||||
    $this->assertEqual($themes[$name]->getName(), $name);
 | 
			
		||||
 | 
			
		||||
    $this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
 | 
			
		||||
 | 
			
		||||
    // Verify that test_basetheme.settings is active.
 | 
			
		||||
    $this->assertIdentical(theme_get_setting('features.favicon', $name), FALSE);
 | 
			
		||||
    $this->assertEqual(theme_get_setting('base', $name), 'only');
 | 
			
		||||
| 
						 | 
				
			
			@ -272,7 +270,6 @@ class ThemeInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->themeInstaller()->uninstall([$name]);
 | 
			
		||||
 | 
			
		||||
    $this->assertFalse(array_keys($this->themeHandler()->listInfo()));
 | 
			
		||||
    $this->assertFalse(array_keys(system_list('theme')));
 | 
			
		||||
 | 
			
		||||
    $this->assertFalse($this->config("$name.settings")->get());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -281,7 +278,6 @@ class ThemeInstallerTest extends KernelTestBase {
 | 
			
		|||
    $themes = $this->themeHandler()->listInfo();
 | 
			
		||||
    $this->assertTrue(isset($themes[$name]));
 | 
			
		||||
    $this->assertEqual($themes[$name]->getName(), $name);
 | 
			
		||||
    $this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
 | 
			
		||||
    $this->assertTrue($this->config("$name.settings")->get());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -331,8 +327,8 @@ class ThemeInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->assertTrue(isset($info['regions']['test_region']));
 | 
			
		||||
    $regions = system_region_list($name);
 | 
			
		||||
    $this->assertTrue(isset($regions['test_region']));
 | 
			
		||||
    $system_list = system_list('theme');
 | 
			
		||||
    $this->assertTrue(isset($system_list[$name]->info['regions']['test_region']));
 | 
			
		||||
    $theme_list = \Drupal::service('theme_handler')->listInfo();
 | 
			
		||||
    $this->assertTrue(isset($theme_list[$name]->info['regions']['test_region']));
 | 
			
		||||
 | 
			
		||||
    $this->moduleInstaller()->uninstall(['module_test']);
 | 
			
		||||
    $this->assertFalse($this->moduleHandler()->moduleExists('module_test'));
 | 
			
		||||
| 
						 | 
				
			
			@ -347,8 +343,8 @@ class ThemeInstallerTest extends KernelTestBase {
 | 
			
		|||
    $this->assertFalse(isset($info['regions']['test_region']));
 | 
			
		||||
    $regions = system_region_list($name);
 | 
			
		||||
    $this->assertFalse(isset($regions['test_region']));
 | 
			
		||||
    $system_list = system_list('theme');
 | 
			
		||||
    $this->assertFalse(isset($system_list[$name]->info['regions']['test_region']));
 | 
			
		||||
    $theme_list = \Drupal::service('theme_handler')->listInfo();
 | 
			
		||||
    $this->assertFalse(isset($theme_list[$name]->info['regions']['test_region']));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,264 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\Tests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Cache\MemoryBackend;
 | 
			
		||||
use Drupal\Core\Cache\NullBackend;
 | 
			
		||||
use Drupal\Core\Extension\Extension;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionDiscovery;
 | 
			
		||||
use Drupal\Core\Extension\InfoParser;
 | 
			
		||||
use Drupal\Core\Extension\InfoParserInterface;
 | 
			
		||||
use Drupal\Core\Extension\ModuleHandlerInterface;
 | 
			
		||||
use Drupal\Core\Extension\ThemeEngineExtensionList;
 | 
			
		||||
use Drupal\Core\Extension\ThemeExtensionList;
 | 
			
		||||
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
 | 
			
		||||
use Drupal\Core\Lock\NullLockBackend;
 | 
			
		||||
use Drupal\Core\State\State;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
use Prophecy\Argument;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Extension\ThemeExtensionList
 | 
			
		||||
 * @group Extension
 | 
			
		||||
 */
 | 
			
		||||
class ThemeExtensionListTest extends UnitTestCase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests rebuild the theme data with theme parents.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRebuildThemeDataWithThemeParents() {
 | 
			
		||||
    $extension_discovery = $this->prophesize(ExtensionDiscovery::class);
 | 
			
		||||
    $extension_discovery
 | 
			
		||||
      ->scan('theme')
 | 
			
		||||
      ->willReturn([
 | 
			
		||||
        'test_subtheme'  => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml', 'test_subtheme.info.yml'),
 | 
			
		||||
        'test_basetheme' => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml', 'test_basetheme.info.yml'),
 | 
			
		||||
      ]);
 | 
			
		||||
    $extension_discovery
 | 
			
		||||
      ->scan('theme_engine')
 | 
			
		||||
      ->willReturn([
 | 
			
		||||
        'twig' => new Extension($this->root, 'theme_engine', $this->root . '/core/themes/engines/twig/twig.info.yml', 'twig.engine'),
 | 
			
		||||
      ]);
 | 
			
		||||
 | 
			
		||||
    // Verify that info parser is called with the specified paths.
 | 
			
		||||
    $argument_condition = function ($path) {
 | 
			
		||||
      return in_array($path, [
 | 
			
		||||
        $this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml',
 | 
			
		||||
        $this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml',
 | 
			
		||||
        $this->root . '/core/themes/engines/twig/twig.info.yml',
 | 
			
		||||
      ], TRUE);
 | 
			
		||||
    };
 | 
			
		||||
    $info_parser = $this->prophesize(InfoParserInterface::class);
 | 
			
		||||
    $info_parser->parse(Argument::that($argument_condition))
 | 
			
		||||
      ->shouldBeCalled()
 | 
			
		||||
      ->will(function ($file) {
 | 
			
		||||
        $info_parser = new InfoParser();
 | 
			
		||||
        return $info_parser->parse($file[0]);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
 | 
			
		||||
    $module_handler
 | 
			
		||||
      ->buildModuleDependencies(Argument::type('array'))
 | 
			
		||||
      ->willReturnArgument(0);
 | 
			
		||||
    $module_handler
 | 
			
		||||
      ->alter('system_info', Argument::type('array'), Argument::type(Extension::class), Argument::any())
 | 
			
		||||
      ->shouldBeCalled();
 | 
			
		||||
 | 
			
		||||
    $state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend());
 | 
			
		||||
 | 
			
		||||
    $config_factory = $this->getConfigFactoryStub([
 | 
			
		||||
      'core.extension' => [
 | 
			
		||||
        'module' => [],
 | 
			
		||||
        'theme' => [],
 | 
			
		||||
        'disabled' => [
 | 
			
		||||
          'theme' => [],
 | 
			
		||||
        ],
 | 
			
		||||
        'theme_engine' => '',
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    $theme_engine_list = new TestThemeEngineExtensionList($this->root, 'theme_engine', new NullBackend('test'), $info_parser->reveal(), $module_handler->reveal(), $state, $config_factory, 'testing');
 | 
			
		||||
    $theme_engine_list->setExtensionDiscovery($extension_discovery->reveal());
 | 
			
		||||
 | 
			
		||||
    $theme_list = new TestThemeExtensionList($this->root, 'theme', new NullBackend('test'), $info_parser->reveal(), $module_handler->reveal(), $state, $config_factory, $theme_engine_list, 'testing');
 | 
			
		||||
    $theme_list->setExtensionDiscovery($extension_discovery->reveal());
 | 
			
		||||
 | 
			
		||||
    $theme_data = $theme_list->reset()->getList();
 | 
			
		||||
    $this->assertCount(2, $theme_data);
 | 
			
		||||
 | 
			
		||||
    $info_basetheme = $theme_data['test_basetheme'];
 | 
			
		||||
    $info_subtheme = $theme_data['test_subtheme'];
 | 
			
		||||
 | 
			
		||||
    // Ensure some basic properties.
 | 
			
		||||
    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_basetheme);
 | 
			
		||||
    $this->assertEquals('test_basetheme', $info_basetheme->getName());
 | 
			
		||||
    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_subtheme);
 | 
			
		||||
    $this->assertEquals('test_subtheme', $info_subtheme->getName());
 | 
			
		||||
 | 
			
		||||
    // Test the parent/child-theme properties.
 | 
			
		||||
    $info_subtheme->info['base theme'] = 'test_basetheme';
 | 
			
		||||
    $info_basetheme->sub_themes = ['test_subtheme'];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_basetheme->owner);
 | 
			
		||||
    $this->assertEquals('twig', $info_basetheme->prefix);
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_subtheme->owner);
 | 
			
		||||
    $this->assertEquals('twig', $info_subtheme->prefix);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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) {
 | 
			
		||||
    // Mocks and stubs.
 | 
			
		||||
    $module_handler = $this->prophesize(ModuleHandlerInterface::class);
 | 
			
		||||
    $state = new State(new KeyValueMemoryFactory(), new MemoryBackend(), new NullLockBackend());
 | 
			
		||||
    $config_factory = $this->getConfigFactoryStub([]);
 | 
			
		||||
    $theme_engine_list = $this->prophesize(ThemeEngineExtensionList::class);
 | 
			
		||||
    $theme_listing = new ThemeExtensionList($this->root, 'theme', new NullBackend('test'), new InfoParser(), $module_handler->reveal(), $state, $config_factory, $theme_engine_list->reveal(), 'test');
 | 
			
		||||
 | 
			
		||||
    $base_themes = $theme_listing->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 = [];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme without any base theme.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_1',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [$themes, 'test_1', []];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme with a non existing base theme.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name'       => 'test_1',
 | 
			
		||||
        'base theme' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [$themes, 'test_1', ['test_2' => NULL]];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme with a single existing base theme.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name'       => 'test_1',
 | 
			
		||||
        'base theme' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $themes['test_2'] = (object) [
 | 
			
		||||
      'name' => 'test_2',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [$themes, 'test_1', ['test_2' => 'test_2']];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme with multiple base themes.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name'       => 'test_1',
 | 
			
		||||
        'base theme' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $themes['test_2'] = (object) [
 | 
			
		||||
      'name' => 'test_2',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name'       => 'test_2',
 | 
			
		||||
        'base theme' => 'test_3',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $themes['test_3'] = (object) [
 | 
			
		||||
      'name' => 'test_3',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_3',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [
 | 
			
		||||
      $themes,
 | 
			
		||||
      'test_1',
 | 
			
		||||
      ['test_2' => 'test_2', 'test_3' => 'test_3'],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Trait that allows extension discovery to be set.
 | 
			
		||||
 */
 | 
			
		||||
trait SettableDiscoveryExtensionListTrait {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The extension discovery for this extension list.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ExtensionDiscovery
 | 
			
		||||
   */
 | 
			
		||||
  protected $extensionDiscovery;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the extension discovery.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Extension\ExtensionDiscovery $discovery
 | 
			
		||||
   *   The extension discovery.
 | 
			
		||||
   */
 | 
			
		||||
  public function setExtensionDiscovery(ExtensionDiscovery $discovery) {
 | 
			
		||||
    $this->extensionDiscovery = $discovery;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getExtensionDiscovery() {
 | 
			
		||||
    return $this->extensionDiscovery;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test theme extension list class.
 | 
			
		||||
 */
 | 
			
		||||
class TestThemeExtensionList extends ThemeExtensionList {
 | 
			
		||||
 | 
			
		||||
  use SettableDiscoveryExtensionListTrait;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test theme engine extension list class.
 | 
			
		||||
 */
 | 
			
		||||
class TestThemeEngineExtensionList extends ThemeEngineExtensionList {
 | 
			
		||||
 | 
			
		||||
  use SettableDiscoveryExtensionListTrait;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (!defined('DRUPAL_MINIMUM_PHP')) {
 | 
			
		||||
  define('DRUPAL_MINIMUM_PHP', '5.5.9');
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -7,11 +7,10 @@
 | 
			
		|||
 | 
			
		||||
namespace Drupal\Tests\Core\Extension;
 | 
			
		||||
 | 
			
		||||
use Composer\Autoload\ClassLoader;
 | 
			
		||||
use Drupal\Core\Extension\Extension;
 | 
			
		||||
use Drupal\Core\Extension\InfoParser;
 | 
			
		||||
use Drupal\Core\Extension\ThemeExtensionList;
 | 
			
		||||
use Drupal\Core\Extension\ThemeHandler;
 | 
			
		||||
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
 | 
			
		||||
use Drupal\Core\State\State;
 | 
			
		||||
use Drupal\Tests\UnitTestCase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -20,20 +19,6 @@ use Drupal\Tests\UnitTestCase;
 | 
			
		|||
 */
 | 
			
		||||
class ThemeHandlerTest extends UnitTestCase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The mocked info parser.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\InfoParserInterface|\PHPUnit_Framework_MockObject_MockObject
 | 
			
		||||
   */
 | 
			
		||||
  protected $infoParser;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The mocked state backend.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
 | 
			
		||||
   */
 | 
			
		||||
  protected $state;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The mocked config factory.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			@ -42,18 +27,11 @@ class ThemeHandlerTest extends UnitTestCase {
 | 
			
		|||
  protected $configFactory;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The mocked module handler.
 | 
			
		||||
   * The theme listing service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
 | 
			
		||||
   * @var \Drupal\Core\Extension\ThemeExtensionList|\PHPUnit_Framework_MockObject_MockObject
 | 
			
		||||
   */
 | 
			
		||||
  protected $moduleHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The extension discovery.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Extension\ExtensionDiscovery|\PHPUnit_Framework_MockObject_MockObject
 | 
			
		||||
   */
 | 
			
		||||
  protected $extensionDiscovery;
 | 
			
		||||
  protected $themeList;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The tested theme handler.
 | 
			
		||||
| 
						 | 
				
			
			@ -77,16 +55,17 @@ class ThemeHandlerTest extends UnitTestCase {
 | 
			
		|||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 | 
			
		||||
    $this->state = new State(new KeyValueMemoryFactory());
 | 
			
		||||
    $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface');
 | 
			
		||||
    $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery')
 | 
			
		||||
    $this->themeList = $this->getMockBuilder(ThemeExtensionList::class)
 | 
			
		||||
      ->disableOriginalConstructor()
 | 
			
		||||
      ->getMock();
 | 
			
		||||
    $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->moduleHandler, $this->state, $this->infoParser, $this->extensionDiscovery);
 | 
			
		||||
    $this->themeHandler = new StubThemeHandler($this->root, $this->configFactory, $this->themeList);
 | 
			
		||||
 | 
			
		||||
    $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
 | 
			
		||||
    $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator);
 | 
			
		||||
    $container = $this->createMock('Symfony\Component\DependencyInjection\ContainerInterface');
 | 
			
		||||
    $container->expects($this->any())
 | 
			
		||||
      ->method('get')
 | 
			
		||||
      ->with('class_loader')
 | 
			
		||||
      ->will($this->returnValue($this->createMock(ClassLoader::class)));
 | 
			
		||||
    \Drupal::setContainer($container);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			@ -95,31 +74,14 @@ class ThemeHandlerTest extends UnitTestCase {
 | 
			
		|||
   * @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData()
 | 
			
		||||
   */
 | 
			
		||||
  public function testRebuildThemeData() {
 | 
			
		||||
    $this->extensionDiscovery->expects($this->at(0))
 | 
			
		||||
      ->method('scan')
 | 
			
		||||
      ->with('theme')
 | 
			
		||||
    $this->themeList->expects($this->at(0))
 | 
			
		||||
      ->method('reset')
 | 
			
		||||
      ->willReturnSelf();
 | 
			
		||||
    $this->themeList->expects($this->at(1))
 | 
			
		||||
      ->method('getList')
 | 
			
		||||
      ->will($this->returnValue([
 | 
			
		||||
        'seven' => new Extension($this->root, 'theme', $this->root . '/core/themes/seven/seven.info.yml', 'seven.theme'),
 | 
			
		||||
      ]));
 | 
			
		||||
    $this->extensionDiscovery->expects($this->at(1))
 | 
			
		||||
      ->method('scan')
 | 
			
		||||
      ->with('theme_engine')
 | 
			
		||||
      ->will($this->returnValue([
 | 
			
		||||
        'twig' => new Extension($this->root, 'theme_engine', $this->root . '/core/themes/engines/twig/twig.info.yml', 'twig.engine'),
 | 
			
		||||
      ]));
 | 
			
		||||
    $this->infoParser->expects($this->once())
 | 
			
		||||
      ->method('parse')
 | 
			
		||||
      ->with($this->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('buildModuleDependencies')
 | 
			
		||||
      ->will($this->returnArgument(0));
 | 
			
		||||
 | 
			
		||||
    $this->moduleHandler->expects($this->once())
 | 
			
		||||
      ->method('alter');
 | 
			
		||||
 | 
			
		||||
    $theme_data = $this->themeHandler->rebuildThemeData();
 | 
			
		||||
    $this->assertCount(1, $theme_data);
 | 
			
		||||
| 
						 | 
				
			
			@ -130,11 +92,7 @@ class ThemeHandlerTest extends UnitTestCase {
 | 
			
		|||
    $this->assertEquals('seven', $info->getName());
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/seven/seven.info.yml', $info->getPathname());
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/seven/seven.theme', $info->getExtensionPathname());
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info->owner);
 | 
			
		||||
    $this->assertEquals('twig', $info->prefix);
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals('twig', $info->info['engine']);
 | 
			
		||||
    $this->assertEquals(['seven/global-styling'], $info->info['libraries']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
| 
						 | 
				
			
			@ -151,158 +109,6 @@ class ThemeHandlerTest extends UnitTestCase {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests rebuild the theme data with theme parents.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRebuildThemeDataWithThemeParents() {
 | 
			
		||||
    $this->extensionDiscovery->expects($this->at(0))
 | 
			
		||||
      ->method('scan')
 | 
			
		||||
      ->with('theme')
 | 
			
		||||
      ->will($this->returnValue([
 | 
			
		||||
        'test_subtheme' => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml', 'test_subtheme.info.yml'),
 | 
			
		||||
        'test_basetheme' => new Extension($this->root, 'theme', $this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml', 'test_basetheme.info.yml'),
 | 
			
		||||
      ]));
 | 
			
		||||
    $this->extensionDiscovery->expects($this->at(1))
 | 
			
		||||
      ->method('scan')
 | 
			
		||||
      ->with('theme_engine')
 | 
			
		||||
      ->will($this->returnValue([
 | 
			
		||||
        'twig' => new Extension($this->root, 'theme_engine', $this->root . '/core/themes/engines/twig/twig.info.yml', 'twig.engine'),
 | 
			
		||||
      ]));
 | 
			
		||||
    $this->infoParser->expects($this->at(0))
 | 
			
		||||
      ->method('parse')
 | 
			
		||||
      ->with($this->root . '/core/modules/system/tests/themes/test_subtheme/test_subtheme.info.yml')
 | 
			
		||||
      ->will($this->returnCallback(function ($file) {
 | 
			
		||||
        $info_parser = new InfoParser();
 | 
			
		||||
        return $info_parser->parse($file);
 | 
			
		||||
      }));
 | 
			
		||||
    $this->infoParser->expects($this->at(1))
 | 
			
		||||
      ->method('parse')
 | 
			
		||||
      ->with($this->root . '/core/modules/system/tests/themes/test_basetheme/test_basetheme.info.yml')
 | 
			
		||||
      ->will($this->returnCallback(function ($file) {
 | 
			
		||||
        $info_parser = new InfoParser();
 | 
			
		||||
        return $info_parser->parse($file);
 | 
			
		||||
      }));
 | 
			
		||||
    $this->moduleHandler->expects($this->once())
 | 
			
		||||
      ->method('buildModuleDependencies')
 | 
			
		||||
      ->will($this->returnArgument(0));
 | 
			
		||||
 | 
			
		||||
    $theme_data = $this->themeHandler->rebuildThemeData();
 | 
			
		||||
    $this->assertCount(2, $theme_data);
 | 
			
		||||
 | 
			
		||||
    $info_basetheme = $theme_data['test_basetheme'];
 | 
			
		||||
    $info_subtheme = $theme_data['test_subtheme'];
 | 
			
		||||
 | 
			
		||||
    // Ensure some basic properties.
 | 
			
		||||
    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_basetheme);
 | 
			
		||||
    $this->assertEquals('test_basetheme', $info_basetheme->getName());
 | 
			
		||||
    $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info_subtheme);
 | 
			
		||||
    $this->assertEquals('test_subtheme', $info_subtheme->getName());
 | 
			
		||||
 | 
			
		||||
    // Test the parent/child-theme properties.
 | 
			
		||||
    $info_subtheme->info['base theme'] = 'test_basetheme';
 | 
			
		||||
    $info_basetheme->sub_themes = ['test_subtheme'];
 | 
			
		||||
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_basetheme->owner);
 | 
			
		||||
    $this->assertEquals('twig', $info_basetheme->prefix);
 | 
			
		||||
    $this->assertEquals($this->root . '/core/themes/engines/twig/twig.engine', $info_subtheme->owner);
 | 
			
		||||
    $this->assertEquals('twig', $info_subtheme->prefix);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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 = [];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme without any base theme.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_1',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [$themes, 'test_1', []];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme with a non existing base theme.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_1',
 | 
			
		||||
        'base theme' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [$themes, 'test_1', ['test_2' => NULL]];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme with a single existing base theme.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_1',
 | 
			
		||||
        'base theme' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $themes['test_2'] = (object) [
 | 
			
		||||
      'name' => 'test_2',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [$themes, 'test_1', ['test_2' => 'test_2']];
 | 
			
		||||
 | 
			
		||||
    // Tests a theme with multiple base themes.
 | 
			
		||||
    $themes = [];
 | 
			
		||||
    $themes['test_1'] = (object) [
 | 
			
		||||
      'name' => 'test_1',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_1',
 | 
			
		||||
        'base theme' => 'test_2',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $themes['test_2'] = (object) [
 | 
			
		||||
      'name' => 'test_2',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_2',
 | 
			
		||||
        'base theme' => 'test_3',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $themes['test_3'] = (object) [
 | 
			
		||||
      'name' => 'test_3',
 | 
			
		||||
      'info' => [
 | 
			
		||||
        'name' => 'test_3',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $data[] = [
 | 
			
		||||
      $themes,
 | 
			
		||||
      'test_1',
 | 
			
		||||
      ['test_2' => 'test_2', 'test_3' => 'test_3'],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    return $data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -324,13 +130,6 @@ class StubThemeHandler extends ThemeHandler {
 | 
			
		|||
   */
 | 
			
		||||
  protected $registryRebuild;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A list of themes keyed by name.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  protected $systemList;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
| 
						 | 
				
			
			@ -345,27 +144,8 @@ class StubThemeHandler extends ThemeHandler {
 | 
			
		|||
    $this->registryRebuild = 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_MINIMUM_PHP')) {
 | 
			
		||||
  define('DRUPAL_MINIMUM_PHP', '5.3.10');
 | 
			
		||||
  define('DRUPAL_MINIMUM_PHP', '5.5.9');
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue