summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel Catchpole2014-02-28 11:51:38 (GMT)
committerNathaniel Catchpole2014-02-28 11:51:38 (GMT)
commit668d277ff6c0dda12443d397b9f0c83fd8d08a2a (patch)
treea908c7330707e881531a9e45405a21419bc848b5
parentdad0245d7dbf6813c5ff649915391b6399a6c516 (diff)
Issue #2188661 by sun, Berdir, andypost: Extension System, Part II: ExtensionDiscovery.
-rw-r--r--core/includes/bootstrap.inc84
-rw-r--r--core/includes/common.inc14
-rw-r--r--core/includes/install.core.inc21
-rw-r--r--core/includes/install.inc27
-rw-r--r--core/includes/module.inc14
-rw-r--r--core/includes/theme.inc23
-rw-r--r--core/lib/Drupal/Core/Config/InstallStorage.php13
-rw-r--r--core/lib/Drupal/Core/DrupalKernel.php26
-rw-r--r--core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php154
-rw-r--r--core/lib/Drupal/Core/Extension/Extension.php223
-rw-r--r--core/lib/Drupal/Core/Extension/ExtensionDiscovery.php408
-rw-r--r--core/lib/Drupal/Core/Extension/InfoParser.php21
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandler.php15
-rw-r--r--core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php5
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeHandler.php71
-rw-r--r--core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php10
-rw-r--r--core/lib/Drupal/Core/Extension/UpdateModuleHandler.php2
-rw-r--r--core/lib/Drupal/Core/SystemListing.php220
-rw-r--r--core/lib/Drupal/Core/SystemListingInfo.php79
-rw-r--r--core/lib/Drupal/Core/Utility/ProjectInfo.php20
-rw-r--r--core/modules/block/tests/modules/block_test/block_test.module8
-rw-r--r--core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php7
-rw-r--r--core/modules/breakpoint/tests/breakpoint_theme_test.info.yml9
-rw-r--r--core/modules/breakpoint/tests/breakpoint_theme_test.module13
-rw-r--r--core/modules/config/tests/config_test/lib/Drupal/config_test/TestInstallStorage.php9
-rw-r--r--core/modules/config/tests/config_test/lib/Drupal/config_test/TestSchemaStorage.php9
-rw-r--r--core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.module8
-rw-r--r--core/modules/locale/locale.compare.inc2
-rw-r--r--core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php2
-rw-r--r--core/modules/simpletest/simpletest.module60
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Bootstrap/GetFilenameUnitTest.php9
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Common/SystemListingTest.php16
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Extension/InfoParserUnitTest.php4
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php9
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/Theme/EntityFilteringThemeTest.php14
-rw-r--r--core/modules/system/system.api.php19
-rw-r--r--core/modules/system/system.module56
-rw-r--r--core/modules/system/tests/fixtures/common_test.info.txt1
-rw-r--r--core/modules/system/tests/fixtures/missing_keys.info.txt1
-rw-r--r--core/modules/system/tests/modules/ajax_test/ajax_test.module8
-rw-r--r--core/modules/system/tests/modules/serialization_test/serialization_test.info.yml7
-rw-r--r--core/modules/system/tests/modules/theme_page_test/theme_page_test.module10
-rw-r--r--core/modules/system/tests/modules/theme_test/theme_test.module11
-rw-r--r--core/modules/update/lib/Drupal/update/UpdateManager.php2
-rw-r--r--core/modules/update/tests/modules/update_test/update_test.module9
-rw-r--r--core/modules/update/update.module2
-rw-r--r--core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php73
-rw-r--r--core/themes/engines/phptemplate/phptemplate.info.yml5
-rw-r--r--core/themes/engines/twig/twig.info.yml5
49 files changed, 1087 insertions, 751 deletions
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 4b6dbb8..851c259 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -10,6 +10,7 @@ use Drupal\Component\Utility\Url;
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 9ecbcb8..ab11ddf 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -19,7 +19,6 @@ use Drupal\Component\PhpStorage\PhpStorageFactory;
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;
@@ -3287,19 +3286,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.
*
* Given the nature of the Drupal page handling, this will be called once with
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 1f88d4a..6b75250 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -13,7 +13,7 @@ use Drupal\Core\Database\Install\TaskException;
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 4374ae8..74d8e39 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -10,6 +10,7 @@ use Drupal\Component\Utility\Crypt;
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 97a69a0..f7404cc 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 5064d6e..7ad60e5 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -12,6 +12,7 @@ use Drupal\Component\Utility\String;
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 @@ const RESPONSIVE_PRIORITY_LOW = 'priority-low';
/**
* 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 @@ const RESPONSIVE_PRIORITY_LOW = 'priority-low';
* @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 16bcc1c..83f7322 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 @@ class InstallStorage extends FileStorage {
*/
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 f9107fe..a763164 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -13,6 +13,7 @@ use Drupal\Core\CoreServiceProvider;
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 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
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 0000000..d5da27c
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator.
+ */
+
+namespace Drupal\Core\Extension\Discovery;
+
+/**
+ * Filters a RecursiveDirectoryIterator to discover extensions.
+ *
+ * To ensure the best possible performance for extension discovery, this
+ * filter implementation hard-codes a range of assumptions about directories
+ * in which Drupal extensions may appear and in which not. Every unnecessary
+ * subdirectory tree recursion is avoided.
+ *
+ * The list of globally ignored directory names is defined in the
+ * RecursiveExtensionFilterIterator::$blacklist property.
+ *
+ * In addition, all 'config' directories are skipped, unless the directory path
+ * ends with 'modules/config', so as to still find the config module provided by
+ * Drupal core and still allow that module to be overridden with a custom config
+ * module.
+ *
+ * Lastly, ExtensionDiscovery instructs this filter to additionally skip all
+ * 'tests' directories at regular runtime, since just with Drupal core only, the
+ * discovery process yields 4x more extensions when tests are not ignored.
+ *
+ * @see ExtensionDiscovery::scan()
+ * @see ExtensionDiscovery::scanDirectory()
+ *
+ * @todo Use RecursiveCallbackFilterIterator instead of the $acceptTests
+ * parameter forwarding once PHP 5.4 is available.
+ */
+class RecursiveExtensionFilterIterator extends \RecursiveFilterIterator {
+
+ /**
+ * List of base extension type directory names to scan.
+ *
+ * Only these directory names are considered when starting a filesystem
+ * recursion in a search path.
+ *
+ * @var array
+ */
+ protected $whitelist = array(
+ 'profiles',
+ 'modules',
+ 'themes',
+ );
+
+ /**
+ * List of directory names to skip when recursing.
+ *
+ * These directories are globally ignored in the recursive filesystem scan;
+ * i.e., extensions (of all types) are not able to use any of these names,
+ * because their directory names will be skipped.
+ *
+ * @var array
+ */
+ protected $blacklist = array(
+ // Object-oriented code subdirectories.
+ 'src',
+ 'lib',
+ 'vendor',
+ // Front-end.
+ 'assets',
+ 'css',
+ 'files',
+ 'images',
+ 'js',
+ 'misc',
+ 'templates',
+ // Legacy subdirectories.
+ 'includes',
+ // Test subdirectories.
+ 'fixtures',
+ // @todo ./tests/Drupal should be ./tests/src/Drupal
+ 'Drupal',
+ );
+
+ /**
+ * Whether to include test directories when recursing.
+ *
+ * @var bool
+ */
+ protected $acceptTests = FALSE;
+
+ /**
+ * Controls whether test directories will be scanned.
+ *
+ * @param bool $flag
+ * Pass FALSE to skip all test directories in the discovery. If TRUE,
+ * extensions in test directories will be discovered and only the global
+ * directory blacklist in RecursiveExtensionFilterIterator::$blacklist is
+ * applied.
+ */
+ public function acceptTests($flag = FALSE) {
+ $this->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 0000000..188350c
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/Extension.php
@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\Extension.
+ */
+
+namespace Drupal\Core\Extension;
+
+/**
+ * Defines an extension (file) object.
+ */
+class Extension implements \Serializable {
+
+ /**
+ * The type of the extension (e.g., 'module').
+ *
+ * @todo Replace all uses of $type with getType() method.
+ *
+ * @var string
+ */
+ public $type;
+
+ /**
+ * The relative pathname of the extension (e.g., 'core/modules/node/node.info.yml').
+ *
+ * @var string
+ */
+ protected $pathname;
+
+ /**
+ * The internal name of the extension (e.g., 'node').
+ *
+ * @todo Replace all uses of $name with getName() method.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * The relative pathname of the main extension file (e.g., 'core/modules/node/node.module').
+ *
+ * @todo Remove this property and do not require .module/.profile files.
+ * @see https://drupal.org/node/340723
+ *
+ * @var string
+ */
+ public $uri;
+
+ /**
+ * The filename of the main extension file (e.g., 'node.module').
+ *
+ * Note that this is not necessarily a filename but a pathname and also not
+ * necessarily the filename of the info file. Due to legacy code and property
+ * value overloading, it is either the filename of the main extension file or
+ * the relative pathname of the main extension file (== $uri), depending on
+ * whether the object has been post-processed or not.
+ *
+ * @see _system_rebuild_module_data()
+ * @see \Drupal\Core\Extension\ThemeHandler::rebuildThemeData()
+ *
+ * @todo Remove this property and do not require .module/.profile files.
+ * @see https://drupal.org/node/340723
+ *
+ * @var string
+ */
+ public $filename;
+
+ /**
+ * An SplFileInfo instance for the extension's info file.
+ *
+ * Note that SplFileInfo is a PHP resource and resources cannot be serialized.
+ *
+ * @var \SplFileInfo
+ */
+ protected $splFileInfo;
+
+ /**
+ * Constructs a new Extension object.
+ *
+ * @param string $type
+ * The type of the extension; e.g., 'module'.
+ * @param string $pathname
+ * The relative path and filename of the extension's info file; e.g.,
+ * 'core/modules/node/node.info.yml'.
+ * @param string $filename
+ * The filename of the main extension file; e.g., 'node.module'.
+ */
+ public function __construct($type, $pathname, $filename) {
+ $this->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 0000000..2d35d00
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -0,0 +1,408 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\ExtensionDiscovery.
+ */
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Component\Utility\Settings;
+use Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator;
+
+/**
+ * Discovers available extensions in the filesystem.
+ */
+class ExtensionDiscovery {
+
+ /**
+ * Origin directory weight: Core.
+ */
+ const ORIGIN_CORE = 0;
+
+ /**
+ * Origin directory weight: Installation profile.
+ */
+ const ORIGIN_PROFILE = 1;
+
+ /**
+ * Origin directory weight: sites/all.
+ */
+ const ORIGIN_SITES_ALL = 2;
+
+ /**
+ * Origin directory weight: Site-wide directory.
+ */
+ const ORIGIN_ROOT = 3;
+
+ /**
+ * Origin directory weight: Parent site directory of a test site environment.
+ */
+ const ORIGIN_PARENT_SITE = 4;
+
+ /**
+ * Origin directory weight: Site-specific directory.
+ */
+ const ORIGIN_SITE = 5;
+
+ /**
+ * Regular expression to match PHP function names.
+ *
+ * @see http://php.net/manual/functions.user-defined.php
+ */
+ const PHP_FUNCTION_PATTERN = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/';
+
+ /**
+ * InfoParser instance for parsing .info.yml files.
+ *
+ * @var \Drupal\Core\Extension\InfoParser
+ */
+ protected $infoParser;
+
+ /**
+ * Previously discovered files keyed by origin directory and extension type.
+ *
+ * @var array
+ */
+ protected static $files = array();
+
+ /**
+ * List of installation profile directories to additionally scan.
+ *
+ * @var array
+ */
+ protected $profileDirectories;
+
+ /**
+ * Discovers available extensions of a given type.
+ *
+ * Finds all extensions (modules, themes, etc) that exist on the site. It
+ * searches in several locations. For instance, to discover all available
+ * modules:
+ * @code
+ * $listing = new ExtensionDiscovery();
+ * $modules = $listing->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 5bb4841..4a23ad8 100644
--- a/core/lib/Drupal/Core/Extension/InfoParser.php
+++ b/core/lib/Drupal/Core/Extension/InfoParser.php
@@ -11,9 +11,8 @@ use Drupal\Component\Utility\String;
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 @@ class InfoParser implements InfoParserInterface {
* 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 a795e0e..1dd6f21 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -599,9 +599,10 @@ class ModuleHandler implements ModuleHandlerInterface {
$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 @@ class ModuleHandler implements ModuleHandlerInterface {
// 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 a74f9cd..3012627 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
@@ -71,7 +71,8 @@ interface ModuleHandlerInterface {
*
* @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 @@ interface ModuleHandlerInterface {
* - 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 82ebad9..fe828ee 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandler.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php
@@ -13,7 +13,6 @@ use Drupal\Core\Cache\CacheBackendInterface;
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 @@ class ThemeHandler implements ThemeHandlerInterface {
* {@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 @@ class ThemeHandler implements ThemeHandlerInterface {
// 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 @@ class ThemeHandler implements ThemeHandlerInterface {
$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 @@ class ThemeHandler implements ThemeHandlerInterface {
// 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 @@ class ThemeHandler implements ThemeHandlerInterface {
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 eef3381..2ba52cd 100644
--- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php
@@ -37,7 +37,7 @@ interface ThemeHandlerInterface {
* 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 @@ interface ThemeHandlerInterface {
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 @@ interface ThemeHandlerInterface {
* 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 1b8a466..4327aac 100644
--- a/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/UpdateModuleHandler.php
@@ -46,8 +46,6 @@ class UpdateModuleHandler extends ModuleHandler {
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 0a82cd2..0000000
--- a/core/lib/Drupal/Core/SystemListing.php
+++ /dev/null
@@ -1,220 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\SystemListing.
- */
-
-namespace Drupal\Core;
-
-use Drupal\Component\Utility\Settings;
-
-/**
- * Returns information about system object files (modules, themes, etc.).
- *
- * This class requires the list of profiles to be scanned (see
- * \Drupal\Core\SystemListing::scan) to be passed into the constructor. Also,
- * info files are not parsed.
- */
-class SystemListing {
-
- /**
- * Construct this listing object.
- *
- * @param array $profiles
- * A list of profiles to search their directories for in addition to the
- * default directories.
- */
- function __construct($profiles = array()) {
- $this->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 d302699..0000000
--- a/core/lib/Drupal/Core/SystemListingInfo.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\SystemListingInfo.
- */
-
-namespace Drupal\Core;
-
-/**
- * Returns information about system object files (modules, themes, etc.).
- *
- * This class finds the profile directories itself and also parses info files.
- */
-class SystemListingInfo extends SystemListing {
-
- /**
- * Overrides Drupal\Core\SystemListing::profiles().
- */
- protected function profiles($directory) {
- $searchdir = array();
- // The 'core/profiles' directory contains pristine collections of modules
- // and themes as provided by a distribution. It is pristine in the same
- // way that the 'core/modules' directory is pristine for core; users
- // should avoid any modification by using the top-level or sites/<domain>
- // 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 0b5090e..e109c78 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 @@ class ProjectInfo {
/**
* 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 4370202..1530f2c 100644
--- a/core/modules/block/tests/modules/block_test/block_test.module
+++ b/core/modules/block/tests/modules/block_test/block_test.module
@@ -6,14 +6,6 @@
*/
/**
- * 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().
*/
function block_test_block_alter(&$block_info) {
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php
index ed88881..153df41 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 @@ use Drupal\breakpoint\Entity\Breakpoint;
*/
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 f035360..0000000
--- 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 765cc50..0000000
--- a/core/modules/breakpoint/tests/breakpoint_theme_test.module
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-/**
- * @file
- * Test breakpoint functionality for breakpoints provided by themes.
- */
-
-/**
- * Implements hook_system_theme_info().
- */
-function breakpoint_theme_test_system_theme_info() {
- $themes['breakpoint_test_theme'] = drupal_get_path('module', 'breakpoint_theme_test') . '/themes/breakpoint_test_theme/breakpoint_test_theme.info.yml';
- return $themes;
-}
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/TestInstallStorage.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/TestInstallStorage.php
index c940422..954036e 100644
--- a/core/modules/config/tests/config_test/lib/Drupal/config_test/TestInstallStorage.php
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/TestInstallStorage.php
@@ -8,6 +8,7 @@
namespace Drupal\config_test;
use Drupal\Core\Config\InstallStorage;
+use Drupal\Core\Extension\ExtensionDiscovery;
/**
* Tests configuration of profiles, modules and themes.
@@ -22,9 +23,11 @@ class TestInstallStorage extends InstallStorage {
*/
protected function getAllFolders() {
if (!isset($this->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 8d240b0..34b8642 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 @@ class TestSchemaStorage extends SchemaStorage {
*/
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 5c4f5d4..c71166f 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 6386dd6..91706fd 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 9928b68..e624057 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -792,7 +792,7 @@ abstract class WebTestBase extends TestBase {
'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 d5a0dd8..8da7e4c 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 4701ce8..a471b09 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 @@ class GetFilenameUnitTest extends UnitTestBase {
// 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 ee1f12b..d37fd4b 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 @@ class SystemListingTest extends WebTestBase {
// 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 e9cf688..8be4af9 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 @@ class InfoParserUnitTest extends DrupalUnitTestBase {
$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 @@ class InfoParserUnitTest extends DrupalUnitTestBase {
$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 554450a..cfd79af 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 @@ class ThemeTest extends WebTestBase {
* 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 8a96631..dda6fb0 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 @@ class EntityFilteringThemeTest extends WebTestBase {
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 @@ class EntityFilteringThemeTest extends WebTestBase {
);
// 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 843629e..63a848e 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -1053,25 +1053,6 @@ function hook_system_breadcrumb_alter(array &$breadcrumb, array $attributes, arr
}
/**
- * 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
*
* This hook is invoked in _system_rebuild_module_data() and in
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 19d5fe0..d36b68f 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 ae97b16..7e57dfe 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 56c6411..c04f34d 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 bca7d69..356f7d6 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -13,14 +13,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 af5617c..0000000
--- 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 6fdd7bd..1180d64 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 667ad83..1c6fceb 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -53,17 +53,6 @@ function theme_test_theme($existing, $type, $theme, $path) {
}
/**
- * 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.
*/
function theme_test_preprocess_html(&$variables) {
diff --git a/core/modules/update/lib/Drupal/update/UpdateManager.php b/core/modules/update/lib/Drupal/update/UpdateManager.php
index 2139fdc..8c7c48a 100644
--- a/core/modules/update/lib/Drupal/update/UpdateManager.php
+++ b/core/modules/update/lib/Drupal/update/UpdateManager.php
@@ -166,7 +166,7 @@ class UpdateManager implements UpdateManagerInterface {
$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 249ad83..ed5e008 100644
--- a/core/modules/update/tests/modules/update_test/update_test.module
+++ b/core/modules/update/tests/modules/update_test/update_test.module
@@ -9,15 +9,6 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
*/
/**
- * 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().
*
* Checks the 'update_test.settings:system_info' configuration and sees if we
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index c2dea88..23865ec 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 2194990..4eab39f 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 @@ class ThemeHandlerTest extends UnitTestCase {
$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,18 +146,13 @@ class ThemeHandlerTest extends UnitTestCase {
->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));
// Ensure the config installer will be called.
@@ -186,27 +182,25 @@ class ThemeHandlerTest extends UnitTestCase {
->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 @@ class ThemeHandlerTest extends UnitTestCase {
$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 @@ class ThemeHandlerTest extends UnitTestCase {
* @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 @@ class ThemeHandlerTest extends UnitTestCase {
$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 0000000..2bea7a0
--- /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 0000000..1767384
--- /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