diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 4b6dbb8b843b63ebab8204f1efdee7b44ee67f97..851c259695d7b0350d25b23eb8c337a028f74c8b 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -10,6 +10,7 @@ use Drupal\Core\DrupalKernel; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Utility\Title; use Drupal\Core\Utility\Error; use Symfony\Component\ClassLoader\ApcClassLoader; @@ -648,7 +649,7 @@ function _drupal_request_initialize() { 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 = array(), $dirs = array(); + static $files = array(); // Profiles are converted into modules in system_rebuild_module_data(). // @todo Remove false-exposure of profiles as modules. @@ -660,72 +661,31 @@ function drupal_get_filename($type, $name, $filename = NULL) { $files[$type] = array(); } - if (!empty($filename)) { + if (isset($filename)) { $files[$type][$name] = $filename; } - elseif (isset($files[$type][$name])) { - // nothing - } - else { - // Verify that we have an keyvalue service before using it. This is required - // because this function is called during installation. - // @todo Inject database connection into KeyValueStore\DatabaseStorage. - if (\Drupal::hasService('keyvalue') && function_exists('db_query')) { - if ($type == 'module') { - if (empty($files[$type])) { - $files[$type] = \Drupal::moduleHandler()->getModuleList(); - } - if (isset($files[$type][$name])) { - return $files[$type][$name]; - } - } - try { - $file_list = \Drupal::state()->get('system.' . $type . '.files'); - if ($file_list && isset($file_list[$name]) && file_exists(DRUPAL_ROOT . '/' . $file_list[$name])) { - $files[$type][$name] = $file_list[$name]; - } - } - catch (Exception $e) { - // The keyvalue service raised an exception because the backend might - // be down. We have a fallback for this case so we hide the error - // completely. - } + elseif (!isset($files[$type][$name])) { + // If the pathname of the requested extension is not known, try to retrieve + // the list of extension pathnames from various providers, checking faster + // providers first. + // Retrieve the current module list (derived from the service container). + if ($type == 'module' && \Drupal::hasService('module_handler')) { + $files[$type] += \Drupal::moduleHandler()->getModuleList(); + } + // If still unknown, retrieve the file list prepared in state by + // system_rebuild_module_data() and system_rebuild_theme_data(). + if (!isset($files[$type][$name]) && \Drupal::hasService('state')) { + $files[$type] += \Drupal::state()->get('system.' . $type . '.files', array()); } - // Fallback to searching the filesystem if the database could not find the - // file or the file returned by the database is not found. + // If still unknown, perform a filesystem scan. if (!isset($files[$type][$name])) { - // We have consistent directory naming: modules, themes... - $dir = $type . 's'; - if ($type == 'theme_engine') { - $dir = 'themes/engines'; - $extension = 'engine'; + $listing = new ExtensionDiscovery(); + // Prevent an infinite recursion by this legacy function. + if ($original_type == 'profile') { + $listing->setProfileDirectories(array()); } - elseif ($type == 'theme') { - $extension = 'info.yml'; - } - // Profiles are converted into modules in system_rebuild_module_data(). - // @todo Remove false-exposure of profiles as modules. - elseif ($original_type == 'profile') { - $dir = 'profiles'; - $extension = 'profile'; - } - else { - $extension = $type; - } - - if (!isset($dirs[$dir][$extension])) { - $dirs[$dir][$extension] = TRUE; - if (!function_exists('drupal_system_listing')) { - require_once __DIR__ . '/common.inc'; - } - // Scan the appropriate directories for all files with the requested - // extension, not just the file we are currently looking for. This - // prevents unnecessary scans from being repeated when this function is - // called more than once in the same page request. - $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir); - foreach ($matches as $matched_name => $file) { - $files[$type][$matched_name] = $file->uri; - } + foreach ($listing->scan($original_type) as $extension_name => $file) { + $files[$type][$extension_name] = $file->uri; } } } diff --git a/core/includes/common.inc b/core/includes/common.inc index 9ecbcb829ea16b84f3b1e890c4ac892bc55033ae..ab11ddffdcfeb1ec093722e76aacd014e2b107cc 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -19,7 +19,6 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Routing\GeneratorNotInitializedException; -use Drupal\Core\SystemListingInfo; use Drupal\Core\Template\Attribute; use Drupal\Core\Render\Element; @@ -3286,19 +3285,6 @@ function drupal_page_set_cache(Response $response, Request $request) { } } -/** - * This function is kept only for backward compatibility. - * - * @see \Drupal\Core\SystemListing::scan(). - */ -function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { - // As SystemListing is required to build a dependency injection container - // from scratch and SystemListingInfo only extends SystemLising, this - // class needs to be hardwired. - $listing = new SystemListingInfo(); - return $listing->scan($mask, $directory, $key, $min_depth); -} - /** * Sets the main page content value for later use. * diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 1f88d4a0cdbe555a401cbd13acd449a39c11ecb2..6b75250d7f565feed9cc2e6e065b2bd337c07ff0 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -13,7 +13,7 @@ use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; use Drupal\Core\StringTranslation\Translator\FileTranslation; - +use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; @@ -494,7 +494,21 @@ function install_begin_request(&$install_state) { // Override the module list with a minimal set of modules. $module_handler->setModuleList(array('system' => 'core/modules/system/system.module')); } - $module_handler->load('system'); + // After setting up a custom and finite module list in a custom low-level + // bootstrap like here, ensure to use ModuleHandler::loadAll() so that + // ModuleHandler::isLoaded() returns TRUE, since that is a condition being + // checked by other subsystems (e.g., the theme system). + $module_handler->loadAll(); + + // Add list of all available profiles to the installation state. + $listing = new ExtensionDiscovery(); + $listing->setProfileDirectories(array()); + $install_state['profiles'] += $listing->scan('profile'); + + // Prime drupal_get_filename()'s static cache. + foreach ($install_state['profiles'] as $name => $profile) { + drupal_get_filename('profile', $name, $profile->uri); + } // Prepare for themed output. We need to run this at the beginning of the // page request to avoid a different theme accidentally getting set. (We also @@ -528,9 +542,6 @@ function install_begin_request(&$install_state) { // Modify the installation state as appropriate. $install_state['completed_task'] = $task; - - // Add the list of available profiles to the installation state. - $install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'); } /** diff --git a/core/includes/install.inc b/core/includes/install.inc index 4374ae8429f46f82a21f2208a30ae3b1e81b3d6b..74d8e3928a05a631e6aaa7db1a18d6380f3658e9 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Settings; use Drupal\Core\Database\Database; use Drupal\Core\DrupalKernel; +use Drupal\Core\Extension\ExtensionDiscovery; /** * Requirement severity -- Informational message only. @@ -123,22 +124,17 @@ function drupal_detect_database_types() { } /** - * Returns all supported database installer objects that are compiled into PHP. + * Returns all supported database driver installer objects. * - * @return - * An array of database installer objects compiled into PHP. + * @return \Drupal\Core\Database\Install\Tasks[] + * An array of available database driver installer objects. */ function drupal_get_database_types() { $databases = array(); $drivers = array(); - // We define a driver as a directory in /core/includes/database that in turn - // contains a database.inc file. That allows us to drop in additional drivers - // without modifying the installer. - require_once __DIR__ . '/database.inc'; - // Allow any valid PHP identifier. - // @see http://www.php.net/manual/en/language.variables.basics.php. - $mask = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/'; + // The internal database driver name is any valid PHP identifier. + $mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/'; $files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, array('recurse' => FALSE)); if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) { $files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, array('recurse' => FALSE)); @@ -584,15 +580,16 @@ function drupal_verify_profile($install_state) { } $info = $install_state['profile_info']; - // Get a list of modules that exist in Drupal's assorted subdirectories. + // Get the list of available modules for the selected installation profile. + $listing = new ExtensionDiscovery(); $present_modules = array(); - foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules') as $present_module) { + foreach ($listing->scan('module') as $present_module) { $present_modules[] = $present_module->name; } // The installation profile is also a module, which needs to be installed // after all the other dependencies have been installed. - $present_modules[] = drupal_get_profile(); + $present_modules[] = $profile; // Verify that all of the profile's required modules are present. $missing_modules = array_diff($info['dependencies'], $present_modules); @@ -974,9 +971,7 @@ function drupal_requirements_url($severity) { function drupal_check_profile($profile, array $install_state) { include_once __DIR__ . '/file.inc'; - $profile_file = $install_state['profiles'][$profile]->uri; - - if (!isset($profile) || !file_exists($profile_file)) { + if (!isset($profile) || !isset($install_state['profiles'][$profile])) { throw new Exception(install_no_profile_error()); } diff --git a/core/includes/module.inc b/core/includes/module.inc index 97a69a0871b0a33055f8bea48f38cd9bd857d433..f7404ccf59face752237089396335460f9173523 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -6,6 +6,7 @@ */ use Drupal\Core\Cache\Cache; +use Drupal\Core\Extension\ExtensionDiscovery; /** * Builds a list of bootstrap modules and enabled modules and themes. @@ -298,14 +299,19 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) * Returns an array of modules required by core. */ function drupal_required_modules() { - $files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'modules'); + $listing = new ExtensionDiscovery(); + $files = $listing->scan('module'); $required = array(); - // An installation profile is required and one must always be loaded. - $required[] = drupal_get_profile(); + // Unless called by the installer, an installation profile is required and + // must always be loaded. drupal_get_profile() also returns the installation + // profile in the installer, but only after it has been selected. + if ($profile = drupal_get_profile()) { + $required[] = $profile; + } foreach ($files as $name => $file) { - $info = \Drupal::service('info_parser')->parse($file->uri); + $info = \Drupal::service('info_parser')->parse($file->getPathname()); if (!empty($info) && !empty($info['required']) && $info['required']) { $required[] = $name; } diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 5064d6eee9e1c713e95d20ee68888c2cdaf18100..7ad60e5c67efcd57ce463e646613927b78585820 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -12,6 +12,7 @@ use Drupal\Component\Utility\Url; use Drupal\Core\Config\Config; use Drupal\Core\Language\Language; +use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\ExtensionNameLengthException; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\RenderWrapper; @@ -64,10 +65,10 @@ /** * Determines if a theme is available to use. * - * @param $theme + * @param string|\Drupal\Core\Extension\Extension $theme * Either the name of a theme or a full theme object. * - * @return + * @return bool * Boolean TRUE if the theme is enabled or is the site administration theme; * FALSE otherwise. * @@ -77,7 +78,7 @@ * @see \Drupal\Core\Theme\ThemeAccessCheck::checkAccess(). */ function drupal_theme_access($theme) { - if (is_object($theme)) { + if ($theme instanceof Extension) { $theme = $theme->name; } return \Drupal::service('access_check.theme')->checkAccess($theme); @@ -120,19 +121,9 @@ function drupal_theme_initialize() { * * This function is useful to initialize a theme when no database is present. * - * @param $theme - * An object with the following information: - * filename - * The .info.yml file for this theme. The 'path' to - * the theme will be in this file's directory. (Required) - * owner - * The path to the .theme file or the .engine file to load for - * the theme. (Required) - * stylesheet - * The primary stylesheet for the theme. (Optional) - * engine - * The name of theme engine to use. (Optional) - * @param $base_theme + * @param \Drupal\Core\Extension\Extension $theme + * The theme extension object. + * @param \Drupal\Core\Extension\Extension[] $base_theme * An optional array of objects that represent the 'base theme' if the * theme is meant to be derivative of another theme. It requires * the same information as the $theme object. It should be in diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php index 16bcc1c3523f36d160eba8f4e1b2c5d4cb1137b1..83f732228aba10bf464ee89003cea64f9e9d7500 100644 --- a/core/lib/Drupal/Core/Config/InstallStorage.php +++ b/core/lib/Drupal/Core/Config/InstallStorage.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Config; +use Drupal\Core\Extension\ExtensionDiscovery; + /** * Storage controller used by the Drupal installer. * @@ -110,9 +112,14 @@ public function listAll($prefix = '') { */ protected function getAllFolders() { if (!isset($this->folders)) { - $this->folders = $this->getComponentNames('profile', array(drupal_get_profile())); - $this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0))); - $this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes'))); + $this->folders = array(); + // @todo Refactor getComponentNames() to use the extension list directly. + if ($profile = drupal_get_profile()) { + $this->folders += $this->getComponentNames('profile', array($profile)); + } + $listing = new ExtensionDiscovery(); + $this->folders += $this->getComponentNames('module', array_keys($listing->scan('module'))); + $this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme'))); } return $this->folders; } diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index f9107febea68b59dddf20fed157c4d681ba03524..a7631642e4a9c32bb9ab50b8adb25e583760a030 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -13,6 +13,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\YamlFileLoader; +use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Language\Language; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -83,7 +84,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * An array of module data objects. * * The data objects have the same data structure as returned by - * file_scan_directory() but only the uri property is used. + * ExtensionDiscovery but only the uri property is used. * * @var array */ @@ -297,19 +298,28 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ protected function moduleData($module) { if (!$this->moduleData) { // First, find profiles. - $profiles_scanner = new SystemListing(); - $all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'); - $profiles = array_keys(array_intersect_key($this->moduleList, $all_profiles)); + $listing = new ExtensionDiscovery(); + $listing->setProfileDirectories(array()); + $all_profiles = $listing->scan('profile'); + $profiles = array_intersect_key($all_profiles, $this->moduleList); + // If a module is within a profile directory but specifies another // profile for testing, it needs to be found in the parent profile. - if (($parent_profile_config = $this->configStorage->read('simpletest.settings')) && isset($parent_profile_config['parent_profile']) && $parent_profile_config['parent_profile'] != $profiles[0]) { + $settings = $this->configStorage->read('simpletest.settings'); + $parent_profile = !empty($settings['parent_profile']) ? $settings['parent_profile'] : NULL; + if ($parent_profile && !isset($profiles[$parent_profile])) { // In case both profile directories contain the same extension, the // actual profile always has precedence. - array_unshift($profiles, $parent_profile_config['parent_profile']); + $profiles = array($parent_profile => $all_profiles[$parent_profile]) + $profiles; } + + $profile_directories = array_map(function ($profile) { + return $profile->getPath(); + }, $profiles); + $listing->setProfileDirectories($profile_directories); + // Now find modules. - $modules_scanner = new SystemListing($profiles); - $this->moduleData = $all_profiles + $modules_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules'); + $this->moduleData = $profiles + $listing->scan('module'); } return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE; } diff --git a/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php b/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php new file mode 100644 index 0000000000000000000000000000000000000000..d5da27cb6ed1b95dfb555aa7124deacd137d2a49 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php @@ -0,0 +1,154 @@ +acceptTests = $flag; + if (!$this->acceptTests) { + $this->blacklist[] = 'tests'; + } + } + + /** + * Overrides \RecursiveFilterIterator::getChildren(). + */ + public function getChildren() { + $filter = parent::getChildren(); + // Pass the $acceptTests flag forward to child iterators. + $filter->acceptTests($this->acceptTests); + return $filter; + } + + /** + * Implements \FilterIterator::accept(). + */ + public function accept() { + $name = $this->current()->getFilename(); + // FilesystemIterator::SKIP_DOTS only skips '.' and '..', but not hidden + // directories (like '.git'). + if ($name[0] == '.') { + return FALSE; + } + if ($this->isDir()) { + // If this is a subdirectory of a base search path, only recurse into the + // fixed list of expected extension type directory names. Required for + // scanning the top-level/root directory; without this condition, we would + // recurse into the whole filesystem tree that possibly contains other + // files aside from Drupal. + if ($this->current()->getSubPath() == '') { + return in_array($name, $this->whitelist, TRUE); + } + // 'config' directories are special-cased here, because every extension + // contains one. However, those default configuration directories cannot + // contain extensions. The directory name cannot be globally skipped, + // because core happens to have a directory of an actual module that is + // named 'config'. By explicitly testing for that case, we can skip all + // other config directories, and at the same time, still allow the core + // config module to be overridden/replaced in a profile/site directory + // (whereas it must be located directly in a modules directory). + if ($name == 'config') { + return substr($this->current()->getPathname(), -14) == 'modules/config'; + } + // Accept the directory unless the name is blacklisted. + return !in_array($name, $this->blacklist, TRUE); + } + else { + // Only accept extension info files. + return substr($name, -9) == '.info.yml'; + } + } + +} diff --git a/core/lib/Drupal/Core/Extension/Extension.php b/core/lib/Drupal/Core/Extension/Extension.php new file mode 100644 index 0000000000000000000000000000000000000000..188350c72b6e7b808e2af059545465aa8e1691fd --- /dev/null +++ b/core/lib/Drupal/Core/Extension/Extension.php @@ -0,0 +1,223 @@ +type = $type; + $this->pathname = $pathname; + // Set legacy public properties. + $this->name = basename($pathname, '.info.yml'); + $this->filename = $filename; + $this->uri = dirname($pathname) . '/' . $filename; + } + + /** + * Returns the type of the extension. + * + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * Returns the internal name of the extension. + * + * @return string + */ + public function getName() { + return basename($this->pathname, '.info.yml'); + } + + /** + * Returns the relative path of the extension. + * + * @return string + */ + public function getPath() { + return dirname($this->pathname); + } + + /** + * Returns the relative path and filename of the extension's info file. + * + * @return string + */ + public function getPathname() { + return $this->pathname; + } + + /** + * Returns the filename of the extension's info file. + * + * @return string + */ + public function getFilename() { + return basename($this->pathname); + } + + /** + * Re-routes method calls to SplFileInfo. + * + * Offers all SplFileInfo methods to consumers; e.g., $extension->getMTime(). + */ + public function __call($method, array $args) { + if (!isset($this->splFileInfo)) { + $this->splFileInfo = new \SplFileInfo($this->pathname); + } + return call_user_func_array(array($this->splFileInfo, $method), $args); + } + + /** + * Sets an explicit SplFileInfo object for the extension's info file. + * + * Used by ExtensionDiscovery::scanDirectory() to avoid creating additional + * PHP resources. + * + * @param \SplFileInfo $fileinfo + * A file info instance to set. + * + * @return $this + */ + public function setSplFileInfo(\SplFileInfo $fileinfo) { + $this->splFileInfo = $fileinfo; + return $this; + } + + /** + * Implements Serializable::serialize(). + * + * Serializes the Extension object in the most optimized way. + */ + public function serialize() { + $data = array( + 'type' => $this->type, + 'pathname' => $this->pathname, + ); + + // Include legacy public properties. + // @todo Remove this property and do not require .module/.profile files. + // @see https://drupal.org/node/340723 + // @see Extension::$filename + $data['filename'] = basename($this->uri); + + // @todo ThemeHandler::listInfo(), ThemeHandler::rebuildThemeData(), and + // system_list() are 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); + } + + return serialize($data); + } + + /** + * Implements Serializable::unserialize(). + */ + public function unserialize($data) { + $data = unserialize($data); + $this->type = $data['type']; + $this->pathname = $data['pathname']; + + // Restore legacy public properties. + // @todo Remove these properties and do not require .module/.profile files. + // @see https://drupal.org/node/340723 + // @see Extension::$filename + $this->name = basename($data['pathname'], '.info.yml'); + $this->uri = dirname($data['pathname']) . '/' . $data['filename']; + $this->filename = $data['filename']; + + // @todo ThemeHandler::listInfo(), ThemeHandler::rebuildThemeData(), and + // system_list() are adding custom properties to the Extension object. + foreach ($data as $property => $value) { + if (!isset($this->$property)) { + $this->$property = $value; + } + } + } + +} diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php new file mode 100644 index 0000000000000000000000000000000000000000..2d35d00c2644b6c3bb8badc14d1bbbab626737a4 --- /dev/null +++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php @@ -0,0 +1,408 @@ +scan('module'); + * @endcode + * + * The following directories will be searched (in the order stated): + * - the core directory; i.e., /core + * - the installation profile directory; e.g., /core/profiles/standard + * - the legacy site-wide directory; i.e., /sites/all + * - the site-wide directory; i.e., / + * - the site-specific directory; e.g., /sites/example.com + * + * The information is returned in an associative array, keyed by the extension + * name (without .info.yml extension). Extensions found later in the search + * will take precedence over extensions found earlier - unless they are not + * compatible with the current version of Drupal core. + * + * @param string $type + * The extension type to search for. One of 'profile', 'module', 'theme', or + * 'theme_engine'. + * @param bool $include_tests + * (optional) Whether to explicitly include or exclude test extensions. By + * default, test extensions are only discovered when in a test environment. + * + * @return \Drupal\Core\Extension\Extension[] + * An associative array of Extension objects, keyed by extension name. + */ + public function scan($type, $include_tests = NULL) { + // Determine the installation profile directories to scan for extensions, + // unless explicit profile directories have been set. + if (!isset($this->profileDirectories)) { + $this->setProfileDirectoriesFromSettings(); + } + + // Search the core directory. + $searchdirs[static::ORIGIN_CORE] = 'core'; + + // Search the legacy sites/all directory. + $searchdirs[static::ORIGIN_SITES_ALL] = 'sites/all'; + + // Search for contributed and custom extensions in top-level directories. + // The scan uses a whitelist to limit recursion to the expected extension + // type specific directory names only. + $searchdirs[static::ORIGIN_ROOT] = ''; + + // Simpletest uses the regular built-in multi-site functionality of Drupal + // for running web tests. As a consequence, extensions of the parent site + // located in a different site-specific directory are not discovered in a + // test site environment, because the site directories are not the same. + // Therefore, add the site directory of the parent site to the search paths, + // so that contained extensions are still discovered. + // @see \Drupal\simpletest\WebTestBase::setUp() + if ($parent_site = Settings::getSingleton()->get('test_parent_site')) { + $searchdirs[static::ORIGIN_PARENT_SITE] = $parent_site; + } + + // Search the site-specific directory. + $searchdirs[static::ORIGIN_SITE] = conf_path(); + + // Unless an explicit value has been passed, manually check whether we are + // in a test environment, in which case test extensions must be included. + if (!isset($include_tests)) { + $include_tests = (bool) drupal_valid_test_ua(); + } + + $files = array(); + foreach ($searchdirs as $dir) { + // Discover all extensions in the directory, unless we did already. + if (!isset(static::$files[$dir][$include_tests])) { + static::$files[$dir][$include_tests] = $this->scanDirectory($dir, $include_tests); + } + // Only return extensions of the requested type. + if (isset(static::$files[$dir][$include_tests][$type])) { + $files += static::$files[$dir][$include_tests][$type]; + } + } + + // Sort the discovered extensions by their originating directories and, + // if applicable, filter out extensions that do not belong to the current + // installation profiles. + $origin_weights = array_flip($searchdirs); + $origins = array(); + $profiles = array(); + foreach ($files as $key => $file) { + // If the extension does not belong to a profile, just apply the weight + // of the originating directory. + if (strpos($file->getSubPath(), 'profiles') !== 0) { + $origins[$key] = $origin_weights[$file->origin]; + $profiles[$key] = NULL; + } + // If the extension belongs to a profile but no profile directories are + // defined, then we are scanning for installation profiles themselves. + // In this case, profiles are sorted by origin only. + elseif (empty($this->profileDirectories)) { + $origins[$key] = static::ORIGIN_PROFILE; + $profiles[$key] = NULL; + } + else { + // Apply the weight of the originating profile directory. + foreach ($this->profileDirectories as $weight => $profile_path) { + if (strpos($file->getPath(), $profile_path) === 0) { + $origins[$key] = static::ORIGIN_PROFILE; + $profiles[$key] = $weight; + continue 2; + } + } + // If we end up here, then the extension does not belong to any of the + // current installation profile directories, so remove it. + unset($files[$key]); + } + } + // Now sort the extensions by origin and installation profile(s). + // The result of this multisort can be depicted like the following matrix, + // whereas the first integer is the weight of the originating directory and + // the second is the weight of the originating installation profile: + // 0 core/modules/node/node.module + // 1 0 profiles/parent_profile/modules/parent_module/parent_module.module + // 1 1 core/profiles/testing/modules/compatible_test/compatible_test.module + // 2 sites/all/modules/common/common.module + // 3 modules/devel/devel.module + // 4 sites/default/modules/custom/custom.module + array_multisort($origins, SORT_ASC, $profiles, SORT_ASC, $files); + + // Process and return the sorted and filtered list of extensions keyed by + // extension name. + return $this->process($files); + } + + /** + * Sets installation profile directories based on current site settings. + * + * @return $this + */ + public function setProfileDirectoriesFromSettings() { + $this->profileDirectories = array(); + $profile = drupal_get_profile(); + // For SimpleTest to be able to test modules packaged together with a + // distribution we need to include the profile of the parent site (in + // which test runs are triggered). + if (drupal_valid_test_ua() && !drupal_installation_attempted()) { + $testing_profile = \Drupal::config('simpletest.settings')->get('parent_profile'); + if ($testing_profile && $testing_profile != $profile) { + $this->profileDirectories[] = drupal_get_path('profile', $testing_profile); + } + } + // In case both profile directories contain the same extension, the actual + // profile always has precedence. + if ($profile) { + $this->profileDirectories[] = drupal_get_path('profile', $profile); + } + return $this; + } + + /** + * Gets the installation profile directories to be scanned. + * + * @return array + * A list of installation profile directory paths relative to the system + * root directory. + */ + public function getProfileDirectories() { + return $this->profileDirectories; + } + + /** + * Sets explicit profile directories to scan. + * + * @param array $paths + * A list of installation profile directory paths relative to the system + * root directory (without trailing slash) to search for extensions. + * + * @return $this + */ + public function setProfileDirectories(array $paths = NULL) { + $this->profileDirectories = $paths; + return $this; + } + + /** + * Processes the filtered and sorted list of extensions. + * + * Extensions discovered in later search paths override earlier, unless they + * are not compatible with the current version of Drupal core. + * + * @param \Drupal\Core\Extension\Extension[] $all_files + * The sorted list of all extensions that were found. + * + * @return \Drupal\Core\Extension\Extension[] + * The filtered list of extensions, keyed by extension name. + */ + protected function process(array $all_files) { + $files = array(); + // Duplicate files found in later search directories take precedence over + // earlier ones; they replace the extension in the existing $files array. + // The exception to this is if the later extension is not compatible with + // the current version of Drupal core, which may occur during upgrades when + // e.g. new modules were introduced in core while older contrib modules with + // the same name still exist in a later search path. + foreach ($all_files as $file) { + if (isset($files[$file->name])) { + // Skip the extension if it is incompatible with Drupal core. + $info = $this->getInfoParser()->parse($file->getPathname()); + if (!isset($info['core']) || $info['core'] != \Drupal::CORE_COMPATIBILITY) { + continue; + } + } + $files[$file->name] = $file; + } + return $files; + } + + /** + * Recursively scans a base directory for the requested extension type. + * + * @param string $dir + * A relative base directory path to scan, without trailing slash. + * @param bool $include_tests + * Whether to include test extensions. If FALSE, all 'tests' directories are + * excluded in the search. + * + * @return array + * An associative array whose keys are extension type names and whose values + * are associative arrays of \Drupal\Core\Extension\Extension objects, keyed + * by absolute path name. + * + * @see \Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator + */ + protected function scanDirectory($dir, $include_tests) { + $files = array(); + + // In order to scan top-level directories, absolute directory paths have to + // be used (which also improves performance, since any configured PHP + // include_paths will not be consulted). Retain the relative originating + // directory being scanned, so relative paths can be reconstructed below + // (all paths are expected to be relative to DRUPAL_ROOT). + $dir_prefix = ($dir == '' ? '' : "$dir/"); + $absolute_dir = ($dir == '' ? DRUPAL_ROOT : DRUPAL_ROOT . "/$dir"); + + if (!is_dir($absolute_dir)) { + return $files; + } + // Use Unix paths regardless of platform, skip dot directories, follow + // symlinks (to allow extensions to be linked from elsewhere), and return + // the RecursiveDirectoryIterator instance to have access to getSubPath(), + // since SplFileInfo does not support relative paths. + $flags = \FilesystemIterator::UNIX_PATHS; + $flags |= \FilesystemIterator::SKIP_DOTS; + $flags |= \FilesystemIterator::FOLLOW_SYMLINKS; + $flags |= \FilesystemIterator::CURRENT_AS_SELF; + $directory_iterator = new \RecursiveDirectoryIterator($absolute_dir, $flags); + + // Filter the recursive scan to discover extensions only. + // Important: Without a RecursiveFilterIterator, RecursiveDirectoryIterator + // would recurse into the entire filesystem directory tree without any kind + // of limitations. + $filter = new RecursiveExtensionFilterIterator($directory_iterator); + $filter->acceptTests($include_tests); + + // The actual recursive filesystem scan is only invoked by instantiating the + // RecursiveIteratorIterator. + $iterator = new \RecursiveIteratorIterator($filter, + \RecursiveIteratorIterator::LEAVES_ONLY, + // Suppress filesystem errors in case a directory cannot be accessed. + \RecursiveIteratorIterator::CATCH_GET_CHILD + ); + + foreach ($iterator as $key => $fileinfo) { + // All extension names in Drupal have to be valid PHP function names due + // to the module hook architecture. + if (!preg_match(static::PHP_FUNCTION_PATTERN, $fileinfo->getBasename('.info.yml'))) { + continue; + } + // Determine extension type from info file. + $type = FALSE; + $file = $fileinfo->openFile('r'); + while (!$type && !$file->eof()) { + preg_match('@^type:\s*(\w+)\s*$@', $file->fgets(), $matches); + if (isset($matches[1])) { + $type = $matches[1]; + } + } + if (empty($type)) { + continue; + } + $name = $fileinfo->getBasename('.info.yml'); + $pathname = $dir_prefix . $fileinfo->getSubPathname(); + + // Supply main extension filename being used throughout Drupal. + // For themes, the filename is the info file itself. + if ($type == 'theme') { + $filename = $fileinfo->getFilename(); + } + // For theme engines, the file extension is .engine. + elseif ($type == 'theme_engine') { + $filename = $name . '.engine'; + } + // Otherwise, it is .module/.profile; i.e., the extension type. + else { + $filename = $name . '.' . $type; + } + + $extension = new Extension($type, $pathname, $filename); + // Inject the existing RecursiveDirectoryIterator object to avoid + // unnecessary creation of additional SplFileInfo resources. + $extension->setSplFileInfo($fileinfo); + // Track the originating directory for sorting purposes. + $extension->origin = $dir; + + $files[$type][$key] = $extension; + } + return $files; + } + + /** + * Returns a parser for .info.yml files. + * + * @return \Drupal\Core\Extension\InfoParser + * The InfoParser instance. + */ + protected function getInfoParser() { + if (!isset($this->infoParser)) { + $this->infoParser = new InfoParser(); + } + return $this->infoParser; + } + +} diff --git a/core/lib/Drupal/Core/Extension/InfoParser.php b/core/lib/Drupal/Core/Extension/InfoParser.php index 5bb484147e9cfd5e6433cddd93848b24b3e1aa7f..4a23ad88f7b6e19d96e1b15a1700a61a56e15211 100644 --- a/core/lib/Drupal/Core/Extension/InfoParser.php +++ b/core/lib/Drupal/Core/Extension/InfoParser.php @@ -11,9 +11,8 @@ use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser; - /** - * Class that parses Drupal module's, theme's and profile's .info.yml files. + * Parses extension .info.yml files. */ class InfoParser implements InfoParserInterface { @@ -22,7 +21,7 @@ class InfoParser implements InfoParserInterface { * * @var array */ - protected $parsedInfos = array(); + protected static $parsedInfos = array(); /** * Symfony YAML parser object. @@ -35,29 +34,29 @@ class InfoParser implements InfoParserInterface { * {@inheritdoc} */ public function parse($filename) { - if (!isset($this->parsedInfos[$filename])) { + if (!isset(static::$parsedInfos[$filename])) { if (!file_exists($filename)) { - $this->parsedInfos[$filename] = array(); + static::$parsedInfos[$filename] = array(); } else { try { - $this->parsedInfos[$filename] = $this->getParser()->parse(file_get_contents($filename)); + static::$parsedInfos[$filename] = $this->getParser()->parse(file_get_contents($filename)); } catch (ParseException $e) { $message = String::format("Unable to parse !file. Parser error !error.", array('!file' => $filename, '!error' => $e->getMessage())); throw new InfoParserException($message, $filename); } - $missing_keys = array_diff($this->getRequiredKeys(), array_keys($this->parsedInfos[$filename])); + $missing_keys = array_diff($this->getRequiredKeys(), array_keys(static::$parsedInfos[$filename])); if (!empty($missing_keys)) { $message = format_plural(count($missing_keys), 'Missing required key (!missing_keys) in !file.', 'Missing required keys (!missing_keys) in !file.', array('!missing_keys' => implode(', ', $missing_keys), '!file' => $filename)); throw new InfoParserException($message, $filename); } - if (isset($this->parsedInfos[$filename]['version']) && $this->parsedInfos[$filename]['version'] === 'VERSION') { - $this->parsedInfos[$filename]['version'] = \Drupal::VERSION; + if (isset(static::$parsedInfos[$filename]['version']) && static::$parsedInfos[$filename]['version'] === 'VERSION') { + static::$parsedInfos[$filename]['version'] = \Drupal::VERSION; } } } - return $this->parsedInfos[$filename]; + return static::$parsedInfos[$filename]; } /** @@ -80,7 +79,7 @@ protected function getParser() { * An array of required keys. */ protected function getRequiredKeys() { - return array('name', 'type'); + return array('type', 'core', 'name'); } } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index a795e0ee5f3afb79f974451a16deeca9c64ddb01..1dd6f21ea104ba6d8d600129bbfaf97ac50c2045 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -599,9 +599,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) { $this->load($module); module_load_install($module); - // Flush theme info caches, since (testing) modules can implement - // hook_system_theme_info() to register additional themes. - system_list_reset(); + // Clear the static cache of system_rebuild_module_data() to pick up the + // new module, since it merges the installation status of modules into + // its statically cached list. + drupal_static_reset('system_rebuild_module_data'); // Update the kernel to include it. // This reboots the kernel to register the module's bundle and its @@ -742,10 +743,10 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Remove any potential cache bins provided by the module. $this->removeCacheBins($module); - // Refresh the system list to exclude the uninstalled modules. - // @todo Only needed to rebuild theme info. - // @see system_list_reset() - system_list_reset(); + // Clear the static cache of system_rebuild_module_data() to pick up the + // new module, since it merges the installation status of modules into + // its statically cached list. + drupal_static_reset('system_rebuild_module_data'); // Clear the entity info cache. entity_info_cache_clear(); diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index a74f9cd3f831de9b46a325784d8950eac8f0a4e7..3012627bb0eea492ed5ba8776cb28768ab73ee97 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -71,7 +71,8 @@ public function setModuleList(array $module_list = array()); * * @param array $modules * An array of module objects keyed by module name. Each object contains - * information discovered during a Drupal\Core\SystemListing scan. + * information discovered during a Drupal\Core\Extension\ExtensionDiscovery + * scan. * * @return * The same array with the new keys for each module: @@ -80,7 +81,7 @@ public function setModuleList(array $module_list = array()); * - required_by: An array with the keys being the modules that will not work * without this module. * - * @see \Drupal\Core\SystemListing + * @see \Drupal\Core\Extension\ExtensionDiscovery */ public function buildModuleDependencies(array $modules); diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index 82ebad92d477270041f35cf80079b9934a1e7b39..fe828ee9e034264aaf56a5b10944704dc15ed04b 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -13,7 +13,6 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigInstallerInterface; use Drupal\Core\Routing\RouteBuilder; -use Drupal\Core\SystemListingInfo; /** * Default theme handler using the config system for enabled/disabled themes. @@ -87,11 +86,11 @@ class ThemeHandler implements ThemeHandlerInterface { protected $routeBuilder; /** - * The system listing info + * An extension discovery instance. * - * @var \Drupal\Core\SystemListingInfo + * @var \Drupal\Core\Extension\ExtensionDiscovery */ - protected $systemListingInfo; + protected $extensionDiscovery; /** * Constructs a new ThemeHandler. @@ -110,17 +109,17 @@ class ThemeHandler implements ThemeHandlerInterface { * database. * @param \Drupal\Core\Routing\RouteBuilder $route_builder * (optional) The route builder to rebuild the routes if a theme is enabled. - * @param \Drupal\Core\SystemListingInfo $system_list_info - * (optional) The system listing info. + * @param \Drupal\Core\Extension\ExtensionDiscovery $extension_discovery + * (optional) A extension discovery instance (for unit tests). */ - public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, SystemListingInfo $system_list_info = NULL) { + public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, InfoParserInterface $info_parser, ConfigInstallerInterface $config_installer = NULL, RouteBuilder $route_builder = NULL, ExtensionDiscovery $extension_discovery = NULL) { $this->configFactory = $config_factory; $this->moduleHandler = $module_handler; $this->cacheBackend = $cache_backend; $this->infoParser = $info_parser; $this->configInstaller = $config_installer; $this->routeBuilder = $route_builder; - $this->systemListingInfo = $system_list_info; + $this->extensionDiscovery = $extension_discovery; } /** @@ -243,24 +242,9 @@ public function reset() { * {@inheritdoc} */ public function rebuildThemeData() { - // Find themes. - $listing = $this->getSystemListingInfo(); - $themes = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes', 'name', 1); - // Allow modules to add further themes. - if ($module_themes = $this->moduleHandler->invokeAll('system_theme_info')) { - foreach ($module_themes as $name => $uri) { - // @see file_scan_directory() - $themes[$name] = (object) array( - 'uri' => $uri, - 'filename' => pathinfo($uri, PATHINFO_FILENAME), - 'name' => $name, - ); - } - } - - // Find theme engines. - $listing = $this->getSystemListingInfo(); - $engines = $listing->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines', 'name', 1); + $listing = $this->getExtensionDiscovery(); + $themes = $listing->scan('theme'); + $engines = $listing->scan('theme_engine'); // Set defaults for theme info. $defaults = array( @@ -288,20 +272,15 @@ public function rebuildThemeData() { // Read info files for each theme. foreach ($themes as $key => $theme) { $themes[$key]->filename = $theme->uri; - $themes[$key]->info = $this->infoParser->parse($theme->uri) + $defaults; - - // Skip this extension if its type is not theme. - if (!isset($themes[$key]->info['type']) || $themes[$key]->info['type'] != 'theme') { - unset($themes[$key]); - continue; - } + $themes[$key]->info = $this->infoParser->parse($theme->getPathname()) + $defaults; // Add the info file modification time, so it becomes available for // contributed modules to use for ordering theme lists. - $themes[$key]->info['mtime'] = filemtime($theme->uri); + $themes[$key]->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', $themes[$key]->info, $themes[$key], $type); @@ -316,12 +295,10 @@ public function rebuildThemeData() { $themes[$key]->template = TRUE; } - // Prefix stylesheets and scripts with module path. - $path = dirname($theme->uri); + // Prefix stylesheets, scripts, and screenshot with theme path. + $path = $theme->getPath(); $theme->info['stylesheets'] = $this->themeInfoPrefixPath($theme->info['stylesheets'], $path); $theme->info['scripts'] = $this->themeInfoPrefixPath($theme->info['scripts'], $path); - - // Give the screenshot proper path information. if (!empty($themes[$key]->info['screenshot'])) { $themes[$key]->info['screenshot'] = $path . '/' . $themes[$key]->info['screenshot']; } @@ -330,7 +307,7 @@ public function rebuildThemeData() { // Now that we've established all our master themes, go back and fill in // data for sub-themes. foreach ($sub_themes as $key) { - $themes[$key]->base_themes = $this->doGetBaseThemes($themes, $key); + $themes[$key]->base_themes = $this->getBaseThemes($themes, $key); // Don't proceed if there was a problem with the root base theme. if (!current($themes[$key]->base_themes)) { continue; @@ -436,23 +413,23 @@ protected function doGetBaseThemes(array $themes, $theme, $used_themes = array() return array($base_key => NULL); } $used_themes[$base_key] = TRUE; - return $this->getBaseThemes($themes, $base_key, $used_themes) + $current_base_theme; + 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 a system listing info object. + * Returns an extension discovery object. * - * @return \Drupal\Core\SystemListingInfo - * The system listing object. + * @return \Drupal\Core\Extension\ExtensionDiscovery + * The extension discovery object. */ - protected function getSystemListingInfo() { - if (!isset($this->systemListingInfo)) { - $this->systemListingInfo = new SystemListingInfo(); + protected function getExtensionDiscovery() { + if (!isset($this->extensionDiscovery)) { + $this->extensionDiscovery = new ExtensionDiscovery(); } - return $this->systemListingInfo; + return $this->extensionDiscovery; } /** diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php index eef3381b410b78e47967634389c04720ffe32d71..2ba52cd3796cef5983e355d1461cc030c7bb926e 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php @@ -37,7 +37,7 @@ public function disable(array $theme_list); * Retrieved from the database, if available and the site is not in * maintenance mode; otherwise compiled freshly from the filesystem. * - * @return array + * @return \Drupal\Core\Extension\Extension[] * An associative array of the currently available themes. The keys are the * themes' machine names and the values are objects having the following * properties: @@ -81,10 +81,10 @@ public function listInfo(); public function reset(); /** - * Helper function to scan and collect theme .info.yml data and their engines. + * Scans and collects theme extension data and their engines. * - * @return array - * An associative array of themes information. + * @return \Drupal\Core\Extension\Extension[] + * An associative array of theme extensions. */ public function rebuildThemeData(); @@ -94,7 +94,7 @@ public function rebuildThemeData(); * Themes can inherit templates and function implementations from earlier * themes. * - * @param array $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. diff --git a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php index 1b8a466176c32e5ac6a5cd553a3f99856ea70445..4327aacdc3793d06d899a0546b511c2a58cb5105 100644 --- a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php @@ -46,8 +46,6 @@ public function getImplementations($hook) { case 'stream_wrappers': return array('system'); - // This is called during rebuild to find testing themes. - case 'system_theme_info': // Those are needed by user_access() to check access on update.php. case 'entity_type_build': case 'entity_load': diff --git a/core/lib/Drupal/Core/SystemListing.php b/core/lib/Drupal/Core/SystemListing.php deleted file mode 100644 index 0a82cd2334ec7e87893a377a5ae785b600b955ba..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/SystemListing.php +++ /dev/null @@ -1,220 +0,0 @@ -profiles = $profiles; - } - - /** - * Returns information about system object files (modules, themes, etc.). - * - * This function is used to find all or some system object files (module - * files, theme files, etc.) that exist on the site. It searches in several - * locations, depending on what type of object you are looking for. For - * instance, if you are looking for modules and call: - * @code - * $scanner = new SystemListing(); - * $all_modules = $scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules'); - * @endcode - * this function will search: - * - the core modules directory; i.e., /core/modules - * - the profiles directories as defined by the profiles() method. - * - the site-wide modules directory; i.e., /modules - * - the all-sites directory; i.e., /sites/all/modules - * - the site-specific directory; i.e., /sites/example.com/modules - * in that order, and return information about all of the files ending in - * .module in those directories. - * - * The information is returned in an associative array, which can be keyed - * on the file name ($key = 'filename'), the file name without the extension - * ($key = 'name'), or the full file stream URI ($key = 'uri'). If you use a - * key of 'filename' or 'name', files found later in the search will take - * precedence over files found earlier (unless they belong to a module or - * theme not compatible with Drupal core); if you choose a key of 'uri', - * you will get all files found. - * - * @param string $mask - * The preg_match() regular expression for the files to find. The - * expression must be anchored and use DRUPAL_PHP_FUNCTION_PATTERN for the - * file name part before the extension, since the results could contain - * matches that do not present valid Drupal extensions otherwise. - * @param string $directory - * The subdirectory name in which the files are found. For example, - * 'modules' will search all 'modules' directories and their - * sub-directories as explained above. - * @param string $key - * (optional) The key to be used for the associative array returned. - * Possible values are: - * - 'uri' for the file's URI. - * - 'filename' for the basename of the file. - * - 'name' for the name of the file without the extension. - * For 'name' and 'filename' only the highest-precedence file is returned. - * Defaults to 'name'. - * - * @return array - * An associative array of file objects, keyed on the chosen key. Each - * element in the array is an object containing file information, with - * properties: - * - 'uri': Full URI of the file. - * - 'filename': File name. - * - 'name': Name of file without the extension. - */ - function scan($mask, $directory, $key = 'name') { - if (!in_array($key, array('uri', 'filename', 'name'))) { - $key = 'uri'; - } - $config = conf_path(); - - // Search for the directory in core. - $searchdir = array('core/' . $directory); - foreach ($this->profiles($directory) as $profile) { - $searchdir[] = $profile; - } - - // Always search for contributed and custom extensions in top-level - // directories as well as sites/all/* directories. If the same extension is - // located in both directories, then the latter wins for legacy/historical - // reasons. - $searchdir[] = $directory; - $searchdir[] = 'sites/all/' . $directory; - - // Simpletest uses the regular built-in multi-site functionality of Drupal - // for running web tests. As a consequence, extensions of the parent site - // located in a different site-specific directory are not discovered in a - // test site environment, because the site directories are not the same. - // Therefore, add the site directory of the parent site to the search paths, - // so that contained extensions are still discovered. - // @see \Drupal\simpletest\WebTestBase::setUp() - if ($parent_site = Settings::getSingleton()->get('test_parent_site')) { - $searchdir[] = $parent_site; - } - if (file_exists("$config/$directory")) { - $searchdir[] = "$config/$directory"; - } - // @todo Find a way to skip ./config directories (but not modules/config). - $nomask = '/^(CVS|lib|templates|css|js)$/'; - $files = array(); - // Get current list of items. - foreach ($searchdir as $dir) { - $files = array_merge($files, $this->process($files, $this->scanDirectory($dir, $key, $mask, $nomask))); - } - return $files; - } - - /** - * List the profiles for this directory. - * - * This version only returns those passed to the constructor. - * - * @param string $directory - * The current search directory like 'modules' or 'themes'. - * - * @return array - * A list of profiles. - */ - protected function profiles($directory) { - return $this->profiles; - } - - /** - * Process the files to add before adding them. - * - * @param array $files - * Every file found so far. - * @param array $files_to_add - * The files found in a single directory. - * - * @return array - * The processed list of file objects. For example, the SystemListingInfo - * class removes files not compatible with the current core version. - */ - protected function process(array $files, array $files_to_add) { - return $files_to_add; - } - - /** - * Abbreviated version of file_scan_directory(). - * - * @param $dir - * The base directory or URI to scan, without trailing slash. - * @param $key - * The key to be used for the returned associative array of files. - * Possible values are 'uri', for the file's URI; 'filename', for the - * basename of the file; and 'name' for the name of the file without the - * extension. - * @param $mask - * The preg_match() regular expression of the files to find. - * @param $nomask - * The preg_match() regular expression of the files to ignore. - * - * @return array - * An associative array (keyed on the chosen key) of objects with 'uri', - * 'filename', and 'name' members corresponding to the matching files. - */ - protected function scanDirectory($dir, $key, $mask, $nomask) { - $files = array(); - if (is_dir($dir)) { - // Avoid warnings when opendir does not have the permissions to open a - // directory. - if ($handle = @opendir($dir)) { - while (FALSE !== ($filename = readdir($handle))) { - // Skip this file if it matches the nomask or starts with a dot. - if ($filename[0] != '.' && !preg_match($nomask, $filename)) { - $uri = "$dir/$filename"; - if (is_dir($uri)) { - // Give priority to files in this folder by merging them in after - // any subdirectory files. - $files = array_merge($this->scanDirectory($uri, $key, $mask, $nomask), $files); - } - elseif (preg_match($mask, $filename)) { - // Always use this match over anything already set in $files with - // the same $options['key']. - $file = new \stdClass(); - $file->uri = $uri; - $file->filename = $filename; - $file->name = pathinfo($filename, PATHINFO_FILENAME); - $this->processFile($file); - $files[$file->$key] = $file; - } - } - } - closedir($handle); - } - } - return $files; - } - - /** - * Process each file object as it is found by scanDirectory(). - * - * @param $file - * A file object. - */ - protected function processFile($file) { - } - -} diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php deleted file mode 100644 index d302699050b26f7beaaaea099fde79966529d0b0..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/SystemListingInfo.php +++ /dev/null @@ -1,79 +0,0 @@ - - // directories. - $profile = drupal_get_profile(); - // For SimpleTest to be able to test modules packaged together with a - // distribution we need to include the profile of the parent site (in - // which test runs are triggered). - if (drupal_valid_test_ua() && !drupal_installation_attempted()) { - $testing_profile = \Drupal::config('simpletest.settings')->get('parent_profile'); - if ($testing_profile && $testing_profile != $profile) { - $searchdir[] = drupal_get_path('profile', $testing_profile) . '/' . $directory; - } - } - // In case both profile directories contain the same extension, the actual - // profile always has precedence. - $searchdir[] = drupal_get_path('profile', $profile) . '/' . $directory; - return $searchdir; - } - - /** - * Overrides Drupal\Core\SystemListing::process(). - */ - protected function process(array $files, array $files_to_add) { - // Duplicate files found in later search directories take precedence over - // earlier ones, so we want them to overwrite keys in our resulting - // $files array. - // The exception to this is if the later file is from a module or theme not - // compatible with Drupal core. This may occur during upgrades of Drupal - // core when new modules exist in core while older contrib modules with the - // same name exist in a directory such as /modules. - foreach (array_intersect_key($files_to_add, $files) as $file_key => $file) { - // If it has no info file, then we just behave liberally and accept the - // new resource on the list for merging. - if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info.yml')) { - // Get the .info.yml file for the module or theme this file belongs to. - $info = \Drupal::service('info_parser')->parse($info_file); - - // If the module or theme is incompatible with Drupal core, remove it - // from the array for the current search directory, so it is not - // overwritten when merged with the $files array. - if (isset($info['core']) && $info['core'] != \Drupal::CORE_COMPATIBILITY) { - unset($files_to_add[$file_key]); - } - } - } - return $files_to_add; - } - - /** - * Overrides Drupal\Core\SystemListing::processFile(). - */ - protected function processFile($file) { - $file->name = basename($file->name, '.info'); - } - -} diff --git a/core/lib/Drupal/Core/Utility/ProjectInfo.php b/core/lib/Drupal/Core/Utility/ProjectInfo.php index 0b5090ec21234b4db4b3931c6bb07387cf3c2e36..e109c784d5bc8ff4c9ebb764bd43fe7bf101fed7 100644 --- a/core/lib/Drupal/Core/Utility/ProjectInfo.php +++ b/core/lib/Drupal/Core/Utility/ProjectInfo.php @@ -29,20 +29,20 @@ class ProjectInfo { * files for each module or theme, which is important data which is used when * deciding if the available update data should be invalidated. * - * @param $projects + * @param array $projects * Reference to the array of project data of what's installed on this site. - * @param $list + * @param \Drupal\Core\Extension\Extension[] $list * Array of data to process to add the relevant info to the $projects array. - * @param $project_type + * @param string $project_type * The kind of data in the list. Can be 'module' or 'theme'. - * @param $status + * @param bool $status * Boolean that controls what status (enabled or disabled) to process out of * the $list and add to the $projects array. - * @param $additional_whitelist + * @param array $additional_whitelist * (optional) Array of additional elements to be collected from the .info.yml * file. Defaults to array(). */ - function processInfoList(&$projects, $list, $project_type, $status, $additional_whitelist = array()) { + function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_whitelist = array()) { foreach ($list as $file) { // A disabled or hidden base theme of an enabled sub-theme still has all // of its code run by the sub-theme, so we include it in our "enabled" @@ -178,13 +178,11 @@ function processInfoList(&$projects, $list, $project_type, $status, $additional_ /** * Determines what project a given file object belongs to. * - * @param $file - * A file object as returned by system_get_files_database(). + * @param \Drupal\Core\Extension\Extension $file + * An extension object. * - * @return + * @return string * The canonical project short name. - * - * @see system_get_files_database() */ function getProjectName($file) { $project_name = ''; diff --git a/core/modules/block/tests/modules/block_test/block_test.module b/core/modules/block/tests/modules/block_test/block_test.module index 437020295df137ae3ba3c45788bb3992f897906a..1530f2c176a9d847035b6a407bbda153590ca8f3 100644 --- a/core/modules/block/tests/modules/block_test/block_test.module +++ b/core/modules/block/tests/modules/block_test/block_test.module @@ -5,14 +5,6 @@ * Provide test blocks. */ -/** - * Implements hook_system_theme_info(). - */ -function block_test_system_theme_info() { - $themes['block_test_theme'] = drupal_get_path('module', 'block_test') . '/themes/block_test_theme/block_test_theme.info.yml'; - return $themes; -} - /** * Implements hook_block_alter(). */ diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php index ed8888190d27aee5abf5958a66ab055fcb2ff780..153df41f936b6f46ed70448833095991d3bd7919 100644 --- a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php +++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php @@ -15,13 +15,6 @@ */ class BreakpointThemeTest extends BreakpointGroupTestBase { - /** - * Modules to enable. - * - * @var array - */ - public static $modules = array('breakpoint_theme_test'); - public static function getInfo() { return array( 'name' => 'Breakpoint theme functionality', diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.info.yml b/core/modules/breakpoint/tests/breakpoint_theme_test.info.yml deleted file mode 100644 index f0353604320131ccdd9241d33946bf2f3794f2e0..0000000000000000000000000000000000000000 --- a/core/modules/breakpoint/tests/breakpoint_theme_test.info.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: 'Breakpoint theme test' -type: module -description: 'Test breakpoints provided by themes' -package: Other -version: VERSION -core: 8.x -hidden: true -dependencies: - - breakpoint diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.module b/core/modules/breakpoint/tests/breakpoint_theme_test.module deleted file mode 100644 index 765cc50686abce3fe3c25c459db5b1b843459ff8..0000000000000000000000000000000000000000 --- a/core/modules/breakpoint/tests/breakpoint_theme_test.module +++ /dev/null @@ -1,13 +0,0 @@ -folders)) { - $this->folders = $this->getComponentNames('profile', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'))); - $this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0))); - $this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes'))); + // @todo Refactor getComponentNames() to use the extension list directly. + $listing = new ExtensionDiscovery(); + $this->folders = $this->getComponentNames('profile', array_keys($listing->scan('profile'))); + $this->folders += $this->getComponentNames('module', array_keys($listing->scan('module'))); + $this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme'))); } return $this->folders; } diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/TestSchemaStorage.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/TestSchemaStorage.php index 8d240b071e80c0e0cf1b44856b47d1a029948472..34b864241e27f9128c4e7ece4b300518ed524aa1 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/TestSchemaStorage.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/TestSchemaStorage.php @@ -8,6 +8,7 @@ namespace Drupal\config_test; use Drupal\Core\Config\Schema\SchemaStorage; +use Drupal\Core\Extension\ExtensionDiscovery; /** * Tests configuration schemas of profiles, modules and themes. @@ -29,10 +30,12 @@ public function __construct() { */ protected function getAllFolders() { if (!isset($this->folders)) { + // @todo Refactor getComponentNames() to use the extension list directly. + $listing = new ExtensionDiscovery(); $this->folders = $this->getBaseDataTypeSchema(); - $this->folders += $this->getComponentNames('profile', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'))); - $this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0))); - $this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes'))); + $this->folders += $this->getComponentNames('profile', array_keys($listing->scan('profile'))); + $this->folders += $this->getComponentNames('module', array_keys($listing->scan('module'))); + $this->folders += $this->getComponentNames('theme', array_keys($listing->scan('theme'))); } return $this->folders; } diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module index 5c4f5d496de67d1762f16a6be13d08e2d5f67a9c..c71166f0087158770ec077ac524a0be543043356 100644 --- a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module +++ b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module @@ -70,11 +70,3 @@ function config_translation_test_form_config_translation_edit_form_alter(&$form, $form['#altered'] = TRUE; } } - -/** - * Implements hook_system_theme_info(). - */ -function config_translation_test_system_theme_info() { - $themes['config_translation_test_theme'] = drupal_get_path('module', 'config_translation') . '/tests/themes/config_translation_test_theme/config_translation_test_theme.info.yml'; - return $themes; -} diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc index 6386dd6513ef31aeb62ae2ad0a7cdf47c06688a8..91706fdab73dee94be8aebf6db7110c5cf1da672 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -162,7 +162,7 @@ function locale_translation_project_list() { * project = myproject" in its .info.yml file. This function will add a project * "myproject" to the info data. * - * @param array $data + * @param \Drupal\Core\Extension\Extension[] $data * Array of .info.yml file data. * @param string $type * The project type. i.e. module, theme. diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 9928b68d686318463c110dbe20e2615bb2e18801..e62405709ff02a6f6baa69739c97719ee0e8d503 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -792,7 +792,7 @@ protected function setUp() { 'required' => TRUE, ); // Add the parent profile's search path to the child site's search paths. - // @see drupal_system_listing() + // @see \Drupal\Core\Extension\ExtensionDiscovery::getProfileDirectories() $settings['conf']['simpletest.settings']['parent_profile'] = (object) array( 'value' => $this->originalProfile, 'required' => TRUE, diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index d5a0dd82fa4c554dd5e1072b346e4223a2ed2dab..8da7e4cd9471b6d74f488369e8a5baeb04e6312b 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -2,6 +2,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Page\HtmlPage; +use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\simpletest\TestBase; use Symfony\Component\Process\PhpExecutableFinder; @@ -430,14 +431,13 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) { * @endcode */ function simpletest_test_get_all($module = NULL) { - $all_groups = &drupal_static(__FUNCTION__); + static $all_groups = array(); $cid = "simpletest:$module"; if (!isset($all_groups[$cid])) { $all_groups[$cid] = array(); $groups = &$all_groups[$cid]; - // Make sure that namespaces for disabled modules are registered so that the - // checks below will find them. + // Register namespaces (extensions are not necessarily enabled). simpletest_classloader_register(); // Load test information from cache if available, otherwise retrieve the @@ -447,24 +447,27 @@ function simpletest_test_get_all($module = NULL) { } else { // Select all PSR-0 classes in the Tests namespace of all modules. - $classes = array(); - $module_data = system_rebuild_module_data(); - $all_data = $module_data + system_rebuild_theme_data(); - $all_data += drupal_system_listing('/\.profile$/', 'profiles', 'name'); + $listing = new ExtensionDiscovery(); + $all_data = $listing->scan('module', TRUE); // If module is set then we keep only that one module. if (isset($module)) { $all_data = array( $module => $all_data[$module], ); } + else { + $all_data += $listing->scan('profile', TRUE); + $all_data += $listing->scan('theme', TRUE); + } + $classes = array(); foreach ($all_data as $name => $data) { // Build directory in which the test files would reside. - $tests_dir = DRUPAL_ROOT . '/' . dirname($data->uri) . '/lib/Drupal/' . $name . '/Tests'; + $tests_dir = DRUPAL_ROOT . '/' . $data->getPath() . '/lib/Drupal/' . $name . '/Tests'; // Scan it for test files if it exists. if (is_dir($tests_dir)) { - $files = file_scan_directory($tests_dir, '/.*\.php/'); + $files = file_scan_directory($tests_dir, '/\.php$/'); if (!empty($files)) { - $basedir = DRUPAL_ROOT . '/' . dirname($data->uri) . '/lib/'; + $basedir = DRUPAL_ROOT . '/' . $data->getPath() . '/lib/'; foreach ($files as $file) { // Convert the file name into the namespaced class name. $replacements = array( @@ -532,23 +535,36 @@ function simpletest_test_get_all($module = NULL) { * Registers namespaces for disabled modules. */ function simpletest_classloader_register() { - // @see drupal_get_filename() + // Use the same cache prefix as simpletest_test_get_all(). + $cid = "simpletest::all"; $types = array( - 'theme_engine' => array('dir' => 'themes/engines', 'extension' => 'engine'), - 'module' => array('dir' => 'modules', 'extension' => 'module'), - 'theme' => array('dir' => 'themes', 'extension' => 'info'), - 'profile' => array('dir' => 'profiles', 'extension' => 'profile'), + 'theme_engine', + 'module', + 'theme', + 'profile', ); - $classloader = drupal_classloader(); + if ($cache = \Drupal::cache()->get($cid)) { + $extensions = $cache->data; + } + else { + $listing = new ExtensionDiscovery(); + $extensions = array(); + foreach ($types as $type) { + foreach ($listing->scan($type, TRUE) as $name => $file) { + $extensions[$type][$name] = $file->uri; + } + } + \Drupal::cache()->set($cid, $extensions); + } - foreach ($types as $type => $info) { - $matches = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.' . $info['extension'] . '$/', $info['dir']); - foreach ($matches as $name => $file) { - drupal_classloader_register($name, dirname($file->uri)); - $classloader->add('Drupal\\' . $name . '\\Tests', DRUPAL_ROOT . '/' . dirname($file->uri) . '/tests'); + $classloader = drupal_classloader(); + foreach ($types as $type) { + foreach ($extensions[$type] as $name => $uri) { + drupal_classloader_register($name, dirname($uri)); + $classloader->add('Drupal\\' . $name . '\\Tests', DRUPAL_ROOT . '/' . dirname($uri) . '/tests'); // While being there, prime drupal_get_filename(). - drupal_get_filename($type, $name, $file->uri); + drupal_get_filename($type, $name, $uri); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/GetFilenameUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/GetFilenameUnitTest.php index 4701ce86dd4bc3c946024a55c7c53838fb97095d..a471b0953a98e421b18b62b5d1921903447acf15 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/GetFilenameUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/GetFilenameUnitTest.php @@ -47,15 +47,6 @@ function testDrupalGetFilename() { // a fixed location and naming. $this->assertIdentical(drupal_get_filename('profile', 'standard'), 'core/profiles/standard/standard.profile', 'Retrieve installation profile location.'); - // When a file is not found in the database cache, drupal_get_filename() - // searches several locations on the filesystem, including the core/ - // directory. We use the '.script' extension below because this is a - // non-existent filetype that will definitely not exist in the database. - // Since there is already a core/scripts directory, drupal_get_filename() - // will automatically check there for 'script' files, just as it does - // for (e.g.) 'module' files in core/modules. - $this->assertIdentical(drupal_get_filename('script', 'test'), 'core/scripts/test/test.script'); - // Searching for an item that does not exist returns NULL. $this->assertNull(drupal_get_filename('module', uniqid("", TRUE)), 'Searching for an item that does not exist returns NULL.'); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/SystemListingTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/SystemListingTest.php index ee1f12b70b06c49373c7b5b35959be34fca7f038..d37fd4b48030723aba61b6510dab12c060296986 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/SystemListingTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/SystemListingTest.php @@ -7,12 +7,13 @@ namespace Drupal\system\Tests\Common; -use Drupal\simpletest\WebTestBase; +use Drupal\Core\Extension\ExtensionDiscovery; +use Drupal\simpletest\DrupalUnitTestBase; /** * Tests scanning system directories in drupal_system_listing(). */ -class SystemListingTest extends WebTestBase { +class SystemListingTest extends DrupalUnitTestBase { public static function getInfo() { return array( 'name' => 'Drupal system listing', @@ -55,11 +56,16 @@ function testDirectoryPrecedence() { // Now scan the directories and check that the files take precedence as // expected. - $files = drupal_system_listing('/\.module$/', 'modules'); + $listing = new ExtensionDiscovery(); + $listing->setProfileDirectories(array('core/profiles/testing')); + $files = $listing->scan('module'); foreach ($expected_directories as $module => $directories) { $expected_directory = array_shift($directories); - $expected_filename = "$expected_directory/$module/$module.module"; - $this->assertEqual($files[$module]->uri, $expected_filename, format_string('Module @module was found at @filename.', array('@module' => $module, '@filename' => $expected_filename))); + $expected_uri = "$expected_directory/$module/$module.module"; + $this->assertEqual($files[$module]->uri, $expected_uri, format_string('Module @actual was found at @expected.', array( + '@actual' => $files[$module]->uri, + '@expected' => $expected_uri, + ))); } } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Extension/InfoParserUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Extension/InfoParserUnitTest.php index e9cf688c8502742277d0d1123f556bf109ede2f6..8be4af9baaa1faba8b641249f7038c3a65b2d8ee 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Extension/InfoParserUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Extension/InfoParserUnitTest.php @@ -72,7 +72,7 @@ public function testInfoParser() { $this->fail('Expected InfoParserException not thrown when reading missing_keys.info.txt'); } catch (InfoParserException $e) { - $expected_message = 'Missing required keys (name, type) in core/modules/system/tests/fixtures/missing_keys.info.txt.'; + $expected_message = "Missing required keys (type, core, name) in $filename."; $this->assertEqual($e->getMessage(), $expected_message); } @@ -83,7 +83,7 @@ public function testInfoParser() { $this->fail('Expected InfoParserException not thrown when reading missing_key.info.txt'); } catch (InfoParserException $e) { - $expected_message = 'Missing required key (type) in core/modules/system/tests/fixtures/missing_key.info.txt.'; + $expected_message = "Missing required key (type) in $filename."; $this->assertEqual($e->getMessage(), $expected_message); } diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php index 554450accbc60ec837543b70b74e90294e675f4e..cfd79af21da720cd62137123b325d08b19b4889e 100644 --- a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php @@ -254,9 +254,12 @@ function testSwitchDefaultTheme() { * Test that themes can't be enabled when the base theme or engine is missing. */ function testInvalidTheme() { - \Drupal::moduleHandler()->install(array('theme_page_test')); + // theme_page_test_system_info_alter() un-hides all hidden themes. + $this->container->get('module_handler')->install(array('theme_page_test')); + // Clear the system_list() and theme listing cache to pick up the change. + $this->container->get('theme_handler')->reset(); $this->drupalGet('admin/appearance'); - $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', array('@base_theme' => 'not_real_test_basetheme')), 'Invalid base theme check succeeded.'); - $this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => 'not_real_engine')), 'Invalid theme engine check succeeded.'); + $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', array('@base_theme' => 'not_real_test_basetheme'))); + $this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => 'not_real_engine'))); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php index 8a96631d2f0dc79d981f6204e11a2666ca93cd21..dda6fb063b153796262c2b00b9658ead6333ef1c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Theme; +use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\comment\CommentInterface; use Drupal\simpletest\WebTestBase; @@ -28,7 +29,7 @@ class EntityFilteringThemeTest extends WebTestBase { /** * A list of all available themes. * - * @var array + * @var \Drupal\Core\Extension\Extension[] */ protected $themes; @@ -81,9 +82,10 @@ public static function getInfo() { function setUp() { parent::setUp(); - // Enable all available themes for testing. - $this->themes = array_keys(list_themes()); - theme_enable($this->themes); + // Enable all available non-testing themes. + $listing = new ExtensionDiscovery(); + $this->themes = $listing->scan('theme', FALSE); + theme_enable(array_keys($this->themes)); // Create a test user. $this->user = $this->drupalCreateUser(array('access content', 'access user profiles')); @@ -133,9 +135,9 @@ function testThemedEntity() { ); // Check each path in all available themes. - foreach ($this->themes as $theme) { + foreach ($this->themes as $name => $theme) { \Drupal::config('system.theme') - ->set('default', $theme) + ->set('default', $name) ->save(); foreach ($paths as $path) { $this->drupalGet($path); diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 843629e3201185e0d6822a9356038389e18343c0..63a848e885b8126a47ef93bd8d6a87d43a6dca68 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -1052,25 +1052,6 @@ function hook_system_breadcrumb_alter(array &$breadcrumb, array $attributes, arr $breadcrumb[] = Drupal::l(t('Text'), 'example_route_name'); } -/** - * Return additional themes provided by modules. - * - * Only use this hook for testing purposes. Use a hidden MYMODULE_test.module - * to implement this hook. Testing themes should be hidden, too. - * - * This hook is invoked from _system_rebuild_theme_data() and allows modules to - * register additional themes outside of the regular 'themes' directories of a - * Drupal installation. - * - * @return - * An associative array. Each key is the system name of a theme and each value - * is the corresponding path to the theme's .info.yml file. - */ -function hook_system_theme_info() { - $themes['mymodule_test_theme'] = drupal_get_path('module', 'mymodule') . '/mymodule_test_theme/mymodule_test_theme.info.yml'; - return $themes; -} - /** * Alter the information parsed from module and theme .info.yml files * diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 19d5fe04e528157c3e3a3823f9ed0e779b317592..d36b68f3a3a49fee1c656eece6abd290823df3ec 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -8,6 +8,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Language\Language; +use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Utility\ModuleInfo; use Drupal\block\BlockPluginInterface; use Drupal\menu_link\MenuLinkInterface; @@ -1522,22 +1523,23 @@ function system_get_info($type, $name = NULL) { /** * Helper function to scan and collect module .info.yml data. * - * @return + * @return \Drupal\Core\Extension\Extension[] * An associative array of module information. */ function _system_rebuild_module_data() { + $listing = new ExtensionDiscovery(); // Find modules - $modules = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0); + $modules = $listing->scan('module'); // Find installation profiles. - $profiles = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles', 'name', 0); + $profiles = $listing->scan('profile'); // Include the installation profile in modules that are loaded. - $profile = drupal_get_profile(); - $modules[$profile] = $profiles[$profile]; - - // Installation profile hooks are always executed last. - $modules[$profile]->weight = 1000; + if ($profile = drupal_get_profile()) { + $modules[$profile] = $profiles[$profile]; + // Installation profile hooks are always executed last. + $modules[$profile]->weight = 1000; + } // Set defaults for module info. $defaults = array( @@ -1555,17 +1557,11 @@ function _system_rebuild_module_data() { $modules[$key]->filename = $module->uri; // Look for the info file. - $module->info = \Drupal::service('info_parser')->parse(dirname($module->uri) . '/' . $module->name . '.info.yml'); - - // Skip modules/profiles that don't provide info or have the wrong type. - if (empty($module->info) || !isset($module->info['type']) || !in_array($module->info['type'], array('module', 'profile'))) { - unset($modules[$key]); - continue; - } + $module->info = \Drupal::service('info_parser')->parse($module->getPathname()); // Add the info file modification time, so it becomes available for // contributed modules to use for ordering module lists. - $module->info['mtime'] = filemtime(dirname($module->uri) . '/' . $module->name . '.info.yml'); + $module->info['mtime'] = $module->getMTime(); // Merge in defaults and save. $modules[$key]->info = $module->info + $defaults; @@ -1578,6 +1574,7 @@ function _system_rebuild_module_data() { // 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 $module->getType(). $type = 'module'; \Drupal::moduleHandler()->alter('system_info', $modules[$key]->info, $modules[$key], $type); } @@ -1589,7 +1586,7 @@ function _system_rebuild_module_data() { } - if (isset($modules[$profile])) { + if ($profile && isset($modules[$profile])) { // The installation profile is required, if it's a valid module. $modules[$profile]->info['required'] = TRUE; // Add a default distribution name if the profile did not provide one. This @@ -1605,9 +1602,9 @@ function _system_rebuild_module_data() { /** * Ensures that dependencies of required modules are also required. * - * @param \stdClass $module + * @param \Drupal\Core\Extension\Extension $module * The module info. - * @param array $modules + * @param \Drupal\Core\Extension\Extension[] $modules * The array of all module info. */ function _system_rebuild_module_data_ensure_required($module, &$modules) { @@ -1626,7 +1623,7 @@ function _system_rebuild_module_data_ensure_required($module, &$modules) { /** * Rebuild, save, and return data about all currently available modules. * - * @return + * @return \Drupal\Core\Extension\Extension[] * Array of all available modules and their data. */ function system_rebuild_module_data() { @@ -1638,14 +1635,13 @@ function system_rebuild_module_data() { $modules = _system_rebuild_module_data(); $files = array(); ksort($modules); - // Add name, status, weight, and schema version. + // Add status, weight, and schema version. $installed_modules = (array) \Drupal::config('system.module')->get('enabled'); - foreach ($modules as $module => $record) { - $record->name = $module; - $record->weight = isset($installed_modules[$module]) ? $installed_modules[$module] : 0; - $record->status = (int) isset($installed_modules[$module]); - $record->schema_version = SCHEMA_UNINSTALLED; - $files[$module] = $record->filename; + foreach ($modules as $name => $module) { + $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0; + $module->status = (int) isset($installed_modules[$name]); + $module->schema_version = SCHEMA_UNINSTALLED; + $files[$name] = $module->uri; } $modules = \Drupal::moduleHandler()->buildModuleDependencies($modules); $modules_cache = $modules; @@ -1660,7 +1656,7 @@ function system_rebuild_module_data() { /** * Helper function to scan and collect theme .info.yml data and their engines. * - * @return + * @return \Drupal\Core\Extension\Extension[] * An associative array of themes information. * * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData() @@ -1675,7 +1671,7 @@ function _system_rebuild_theme_data() { /** * Rebuild, save, and return data about all currently available themes. * - * @return + * @return \Drupal\Core\Extension\Extension[] * Array of all available themes and their data. */ function system_rebuild_theme_data() { @@ -1692,7 +1688,7 @@ function system_rebuild_theme_data() { $files = array(); foreach ($themes as $name => $theme) { $theme->status = (int) isset($enabled_themes[$name]); - $files[$name] = $theme->filename; + $files[$name] = $theme->uri; } // Replace last known theme data state. // @todo Obsolete with proper installation status for themes. diff --git a/core/modules/system/tests/fixtures/common_test.info.txt b/core/modules/system/tests/fixtures/common_test.info.txt index ae97b16f536bb74933e43a4763511d8f3e1f539b..7e57dfe4bfe616383eb5ac364e38fd2bbb7021c7 100644 --- a/core/modules/system/tests/fixtures/common_test.info.txt +++ b/core/modules/system/tests/fixtures/common_test.info.txt @@ -1,3 +1,4 @@ +core: 8.x name: common_test type: module description: 'testing info file parsing' diff --git a/core/modules/system/tests/fixtures/missing_keys.info.txt b/core/modules/system/tests/fixtures/missing_keys.info.txt index 56c6411e3232162be263c701ef19326f726939ac..c04f34d7c0f69a5403c71ab0229db925ed23310b 100644 --- a/core/modules/system/tests/fixtures/missing_keys.info.txt +++ b/core/modules/system/tests/fixtures/missing_keys.info.txt @@ -1,6 +1,5 @@ # info.yml for testing missing name, description, and type keys. package: Core version: VERSION -core: 8.x dependencies: - field diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module index bca7d69c7dd716d3a330eb98f71e7d58cb6bbd68..356f7d699114f436f3d4575ddc9da00718463d4c 100644 --- a/core/modules/system/tests/modules/ajax_test/ajax_test.module +++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module @@ -12,14 +12,6 @@ use Drupal\Core\Ajax\CloseDialogCommand; use Drupal\Core\Ajax\HtmlCommand; -/** - * Implements hook_system_theme_info(). - */ -function ajax_test_system_theme_info() { - $themes['test_theme'] = drupal_get_path('module', 'system') . '/tests/themes/test_theme/test_theme.info.yml'; - return $themes; -} - /** * Menu callback: Returns an element suitable for use by * \Drupal\Core\Ajax\AjaxResponse::ajaxRender(). diff --git a/core/modules/system/tests/modules/serialization_test/serialization_test.info.yml b/core/modules/system/tests/modules/serialization_test/serialization_test.info.yml deleted file mode 100644 index af5617ccf329198a559fc348c64cf116900bba40..0000000000000000000000000000000000000000 --- a/core/modules/system/tests/modules/serialization_test/serialization_test.info.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: 'Serialization test module' -type: module -description: 'Support module for serialization tests.' -package: Testing -version: VERSION -core: 8.x -hidden: true diff --git a/core/modules/system/tests/modules/theme_page_test/theme_page_test.module b/core/modules/system/tests/modules/theme_page_test/theme_page_test.module index 6fdd7bd3d19247764dc4ee1f972de4e2b41b7a14..1180d64fa855fa8a56ecaa0b125133d2f8e69afb 100644 --- a/core/modules/system/tests/modules/theme_page_test/theme_page_test.module +++ b/core/modules/system/tests/modules/theme_page_test/theme_page_test.module @@ -9,13 +9,3 @@ function theme_page_test_system_info_alter(&$info, $file, $type) { unset($info['hidden']); } } - - -/** - * Implements hook_system_theme_info(). - */ -function theme_page_test_system_theme_info() { - $themes['test_invalid_basetheme'] = drupal_get_path('module', 'system') . '/tests/themes/test_invalid_basetheme/test_invalid_basetheme.info.yml'; - $themes['test_invalid_engine'] = drupal_get_path('module', 'system') . '/tests/themes/test_invalid_engine/test_invalid_engine.info.yml'; - return $themes; -} diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module index 667ad83759e7279aaafd3b27f8832c73403bbecd..1c6fceb888266355653fc613ab6f6f4d22049bca 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -52,17 +52,6 @@ function theme_test_theme($existing, $type, $theme, $path) { return $items; } -/** - * Implements hook_system_theme_info(). - */ -function theme_test_system_theme_info() { - $themes['test_theme'] = drupal_get_path('module', 'system') . '/tests/themes/test_theme/test_theme.info.yml'; - $themes['test_basetheme'] = drupal_get_path('module', 'system') . '/tests/themes/test_basetheme/test_basetheme.info.yml'; - $themes['test_subtheme'] = drupal_get_path('module', 'system') . '/tests/themes/test_subtheme/test_subtheme.info.yml'; - $themes['test_theme_phptemplate'] = drupal_get_path('module', 'system') . '/tests/themes/test_theme_phptemplate/test_theme_phptemplate.info.yml'; - return $themes; -} - /** * Implements hook_preprocess_HOOK() for HTML document templates. */ diff --git a/core/modules/update/lib/Drupal/update/UpdateManager.php b/core/modules/update/lib/Drupal/update/UpdateManager.php index 2139fdc515098dde0a0b5fb01b394a87cc74fa84..8c7c48abdd028c9174b99716e66db03b044ed9d4 100644 --- a/core/modules/update/lib/Drupal/update/UpdateManager.php +++ b/core/modules/update/lib/Drupal/update/UpdateManager.php @@ -166,7 +166,7 @@ public function projectStorage($key) { $this->keyValueStore->delete($key); } else { - $projects = $this->keyValueStore->get($key); + $projects = $this->keyValueStore->get($key, array()); } return $projects; } diff --git a/core/modules/update/tests/modules/update_test/update_test.module b/core/modules/update/tests/modules/update_test/update_test.module index 249ad83a9ea0c81e2971aa9dde3377bbab506a59..ed5e008ec564cc5256034efc6d88f779ccaa6182 100644 --- a/core/modules/update/tests/modules/update_test/update_test.module +++ b/core/modules/update/tests/modules/update_test/update_test.module @@ -8,15 +8,6 @@ * Module for testing Update Manager functionality. */ -/** - * Implements hook_system_theme_info(). - */ -function update_test_system_theme_info() { - $themes['update_test_basetheme'] = drupal_get_path('module', 'update') . '/tests/themes/update_test_basetheme/update_test_basetheme.info.yml'; - $themes['update_test_subtheme'] = drupal_get_path('module', 'update') . '/tests/themes/update_test_subtheme/update_test_subtheme.info.yml'; - return $themes; -} - /** * Implements hook_system_info_alter(). * diff --git a/core/modules/update/update.module b/core/modules/update/update.module index c2dea88a9b91baebf4804ce201166fc8113cbb88..23865eca015dce9391c5a03e971de2a476e306e3 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -596,7 +596,7 @@ function theme_update_last_check($variables) { * an .info.yml file which claims that the code is compatible with the current * version of Drupal core. * - * @see drupal_system_listing() + * @see \Drupal\Core\Extension\ExtensionDiscovery * @see _system_rebuild_module_data() */ function update_verify_update_archive($project, $archive_file, $directory) { diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php index 2194990ac787d6d3095526b87ede5b268944db94..4eab39fa0d6da9c32dbe08a5c9ba8df0ef36ae36 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Extension; +use Drupal\Core\Extension\Extension; use Drupal\Core\Extension\InfoParser; use Drupal\Core\Extension\ThemeHandler; use Drupal\Core\Config\ConfigInstaller; @@ -65,11 +66,11 @@ class ThemeHandlerTest extends UnitTestCase { protected $configInstaller; /** - * The system listing info. + * The extension discovery. * - * @var \Drupal\Core\SystemListingInfo|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Extension\ExtensionDiscovery|\PHPUnit_Framework_MockObject_MockObject */ - protected $systemListingInfo; + protected $extensionDiscovery; /** * The tested theme handler. @@ -101,10 +102,10 @@ protected function setUp() { $this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder') ->disableOriginalConstructor() ->getMock(); - $this->systemListingInfo = $this->getMockBuilder('Drupal\Core\SystemListingInfo') + $this->extensionDiscovery = $this->getMockBuilder('Drupal\Core\Extension\ExtensionDiscovery') ->disableOriginalConstructor() ->getMock(); - $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->systemListingInfo); + $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery); $this->getContainerWithCacheBins($this->cacheBackend); } @@ -145,17 +146,12 @@ public function testEnableSingleTheme() { ->expects($this->once()) ->method('save'); - $this->systemListingInfo->expects($this->any()) + $this->extensionDiscovery->expects($this->any()) ->method('scan') ->will($this->returnValue(array())); // Ensure that the themes_enabled hook is fired. $this->moduleHandler->expects($this->at(0)) - ->method('invokeAll') - ->with('system_theme_info') - ->will($this->returnValue(array())); - - $this->moduleHandler->expects($this->at(1)) ->method('invokeAll') ->with('themes_enabled', array($theme_list)); @@ -186,27 +182,25 @@ public function testEnableAndListInfo() { ->method('clear') ->will($this->returnSelf()); - $this->systemListingInfo->expects($this->any()) + $this->extensionDiscovery->expects($this->any()) ->method('scan') ->will($this->returnValue(array())); $this->themeHandler->enable(array('bartik')); - $this->themeHandler->systemList['bartik'] = (object) array( - 'name' => 'bartik', - 'info' => array( - 'stylesheets' => array( - 'all' => array( - 'css/layout.css', - 'css/style.css', - 'css/colors.css', - ), + $this->themeHandler->systemList['bartik'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/bartik/bartik.info.yml', 'bartik.info.yml'); + $this->themeHandler->systemList['bartik']->info = array( + 'stylesheets' => array( + 'all' => array( + 'css/layout.css', + 'css/style.css', + 'css/colors.css', ), - 'scripts' => array( - 'example' => 'theme.js', - ), - 'engine' => 'twig', - 'base theme' => 'stark', ), + 'scripts' => array( + 'example' => 'theme.js', + ), + 'engine' => 'twig', + 'base theme' => 'stark', ); $list_info = $this->themeHandler->listInfo(); @@ -218,18 +212,16 @@ public function testEnableAndListInfo() { $this->assertEquals('stark', $list_info['bartik']->base_theme); $this->assertEquals(0, $list_info['bartik']->status); - $this->themeHandler->systemList['seven'] = (object) array( - 'name' => 'seven', - 'info' => array( - 'stylesheets' => array( - 'screen' => array( - 'style.css', - ), + $this->themeHandler->systemList['seven'] = new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml'); + $this->themeHandler->systemList['seven']->info = array( + 'stylesheets' => array( + 'screen' => array( + 'style.css', ), - 'scripts' => array(), ), - 'status' => 1, + 'scripts' => array(), ); + $this->themeHandler->systemList['seven']->status = 1; $this->themeHandler->enable(array('seven')); @@ -246,14 +238,11 @@ public function testEnableAndListInfo() { * @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData() */ public function testRebuildThemeData() { - $this->systemListingInfo->expects($this->at(0)) + $this->extensionDiscovery->expects($this->at(0)) ->method('scan') - ->with($this->anything(), 'themes', 'name', 1) + ->with('theme') ->will($this->returnValue(array( - 'seven' => (object) array( - 'name' => 'seven', - 'uri' => DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', - ), + 'seven' => new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml'), ))); $this->infoParser->expects($this->once()) ->method('parse') @@ -271,7 +260,7 @@ public function testRebuildThemeData() { $info = $theme_data['seven']; // Ensure some basic properties. - $this->assertInstanceOf('stdClass', $info); + $this->assertInstanceOf('Drupal\Core\Extension\Extension', $info); $this->assertEquals('seven', $info->name); $this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', $info->uri); $this->assertEquals(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', $info->filename); diff --git a/core/themes/engines/phptemplate/phptemplate.info.yml b/core/themes/engines/phptemplate/phptemplate.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..2bea7a07c69bb56fb86b33aa08dd6659e4802051 --- /dev/null +++ b/core/themes/engines/phptemplate/phptemplate.info.yml @@ -0,0 +1,5 @@ +type: theme_engine +name: PHPTemplate +core: 8.x +version: VERSION +package: Core diff --git a/core/themes/engines/twig/twig.info.yml b/core/themes/engines/twig/twig.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..17673841c8b895c9a8576b69a33e001bd64bc7e5 --- /dev/null +++ b/core/themes/engines/twig/twig.info.yml @@ -0,0 +1,5 @@ +type: theme_engine +name: Twig +core: 8.x +version: VERSION +package: Core