Commit ae8be3cc authored by catch's avatar catch

Issue #2188661 by sun, Berdir: Extension System, Part II: ExtensionDiscovery.

parent 438494d0
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Drupal\Core\DrupalKernel; use Drupal\Core\DrupalKernel;
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Utility\Title; use Drupal\Core\Utility\Title;
use Drupal\Core\Utility\Error; use Drupal\Core\Utility\Error;
use Symfony\Component\ClassLoader\ApcClassLoader; use Symfony\Component\ClassLoader\ApcClassLoader;
...@@ -655,7 +656,7 @@ function _drupal_request_initialize() { ...@@ -655,7 +656,7 @@ function _drupal_request_initialize() {
function drupal_get_filename($type, $name, $filename = NULL) { function drupal_get_filename($type, $name, $filename = NULL) {
// The location of files will not change during the request, so do not use // The location of files will not change during the request, so do not use
// drupal_static(). // drupal_static().
static $files = array(), $dirs = array(); static $files = array();
// Profiles are converted into modules in system_rebuild_module_data(). // Profiles are converted into modules in system_rebuild_module_data().
// @todo Remove false-exposure of profiles as modules. // @todo Remove false-exposure of profiles as modules.
...@@ -667,72 +668,31 @@ function drupal_get_filename($type, $name, $filename = NULL) { ...@@ -667,72 +668,31 @@ function drupal_get_filename($type, $name, $filename = NULL) {
$files[$type] = array(); $files[$type] = array();
} }
if (!empty($filename)) { if (isset($filename)) {
$files[$type][$name] = $filename; $files[$type][$name] = $filename;
} }
elseif (isset($files[$type][$name])) { elseif (!isset($files[$type][$name])) {
// nothing // If the pathname of the requested extension is not known, try to retrieve
} // the list of extension pathnames from various providers, checking faster
else { // providers first.
// Verify that we have an keyvalue service before using it. This is required // Retrieve the current module list (derived from the service container).
// because this function is called during installation. if ($type == 'module' && \Drupal::hasService('module_handler')) {
// @todo Inject database connection into KeyValueStore\DatabaseStorage. $files[$type] += \Drupal::moduleHandler()->getModuleList();
if (\Drupal::hasService('keyvalue') && function_exists('db_query')) { }
if ($type == 'module') { // If still unknown, retrieve the file list prepared in state by
if (empty($files[$type])) { // system_rebuild_module_data() and system_rebuild_theme_data().
$files[$type] = \Drupal::moduleHandler()->getModuleList(); if (!isset($files[$type][$name]) && \Drupal::hasService('state')) {
} $files[$type] += \Drupal::state()->get('system.' . $type . '.files', array());
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.
}
} }
// Fallback to searching the filesystem if the database could not find the // If still unknown, perform a filesystem scan.
// file or the file returned by the database is not found.
if (!isset($files[$type][$name])) { if (!isset($files[$type][$name])) {
// We have consistent directory naming: modules, themes... $listing = new ExtensionDiscovery();
$dir = $type . 's'; // Prevent an infinite recursion by this legacy function.
if ($type == 'theme_engine') { if ($original_type == 'profile') {
$dir = 'themes/engines'; $listing->setProfileDirectories(array());
$extension = 'engine';
} }
elseif ($type == 'theme') { foreach ($listing->scan($original_type) as $extension_name => $file) {
$extension = 'info.yml'; $files[$type][$extension_name] = $file->uri;
}
// 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;
}
} }
} }
} }
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Routing\GeneratorNotInitializedException; use Drupal\Core\Routing\GeneratorNotInitializedException;
use Drupal\Core\SystemListingInfo;
use Drupal\Core\Template\Attribute; use Drupal\Core\Template\Attribute;
use Drupal\Core\Render\Element; use Drupal\Core\Render\Element;
...@@ -3303,19 +3302,6 @@ function drupal_page_set_cache(Response $response, Request $request) { ...@@ -3303,19 +3302,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. * Sets the main page content value for later use.
* *
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager; use Drupal\Core\Language\LanguageManager;
use Drupal\Core\StringTranslation\Translator\FileTranslation; use Drupal\Core\StringTranslation\Translator\FileTranslation;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
...@@ -494,7 +494,21 @@ function install_begin_request(&$install_state) { ...@@ -494,7 +494,21 @@ function install_begin_request(&$install_state) {
// Override the module list with a minimal set of modules. // Override the module list with a minimal set of modules.
$module_handler->setModuleList(array('system' => 'core/modules/system/system.module')); $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 // 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 // page request to avoid a different theme accidentally getting set. (We also
...@@ -528,9 +542,6 @@ function install_begin_request(&$install_state) { ...@@ -528,9 +542,6 @@ function install_begin_request(&$install_state) {
// Modify the installation state as appropriate. // Modify the installation state as appropriate.
$install_state['completed_task'] = $task; $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');
} }
/** /**
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Drupal\Component\Utility\Settings; use Drupal\Component\Utility\Settings;
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel; use Drupal\Core\DrupalKernel;
use Drupal\Core\Extension\ExtensionDiscovery;
/** /**
* Requirement severity -- Informational message only. * Requirement severity -- Informational message only.
...@@ -123,22 +124,17 @@ function drupal_detect_database_types() { ...@@ -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 * @return \Drupal\Core\Database\Install\Tasks[]
* An array of database installer objects compiled into PHP. * An array of available database driver installer objects.
*/ */
function drupal_get_database_types() { function drupal_get_database_types() {
$databases = array(); $databases = array();
$drivers = array(); $drivers = array();
// We define a driver as a directory in /core/includes/database that in turn // The internal database driver name is any valid PHP identifier.
// contains a database.inc file. That allows us to drop in additional drivers $mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/';
// 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]*$/';
$files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, array('recurse' => FALSE)); $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')) { 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)); $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) { ...@@ -584,15 +580,16 @@ function drupal_verify_profile($install_state) {
} }
$info = $install_state['profile_info']; $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(); $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; $present_modules[] = $present_module->name;
} }
// The installation profile is also a module, which needs to be installed // The installation profile is also a module, which needs to be installed
// after all the other dependencies have been 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. // Verify that all of the profile's required modules are present.
$missing_modules = array_diff($info['dependencies'], $present_modules); $missing_modules = array_diff($info['dependencies'], $present_modules);
...@@ -976,9 +973,7 @@ function drupal_requirements_url($severity) { ...@@ -976,9 +973,7 @@ function drupal_requirements_url($severity) {
function drupal_check_profile($profile, array $install_state) { function drupal_check_profile($profile, array $install_state) {
include_once __DIR__ . '/file.inc'; include_once __DIR__ . '/file.inc';
$profile_file = $install_state['profiles'][$profile]->uri; if (!isset($profile) || !isset($install_state['profiles'][$profile])) {
if (!isset($profile) || !file_exists($profile_file)) {
throw new Exception(install_no_profile_error()); throw new Exception(install_no_profile_error());
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Extension\ExtensionDiscovery;
/** /**
* Builds a list of bootstrap modules and enabled modules and themes. * Builds a list of bootstrap modules and enabled modules and themes.
...@@ -298,14 +299,19 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) ...@@ -298,14 +299,19 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
* Returns an array of modules required by core. * Returns an array of modules required by core.
*/ */
function drupal_required_modules() { function drupal_required_modules() {
$files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'modules'); $listing = new ExtensionDiscovery();
$files = $listing->scan('module');
$required = array(); $required = array();
// An installation profile is required and one must always be loaded. // Unless called by the installer, an installation profile is required and
$required[] = drupal_get_profile(); // 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) { 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']) { if (!empty($info) && !empty($info['required']) && $info['required']) {
$required[] = $name; $required[] = $name;
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
use Drupal\Component\Utility\Url; use Drupal\Component\Utility\Url;
use Drupal\Core\Config\Config; use Drupal\Core\Config\Config;
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ExtensionNameLengthException; use Drupal\Core\Extension\ExtensionNameLengthException;
use Drupal\Core\Template\Attribute; use Drupal\Core\Template\Attribute;
use Drupal\Core\Template\RenderWrapper; use Drupal\Core\Template\RenderWrapper;
...@@ -65,10 +66,10 @@ ...@@ -65,10 +66,10 @@
/** /**
* Determines if a theme is available to use. * 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. * 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; * Boolean TRUE if the theme is enabled or is the site administration theme;
* FALSE otherwise. * FALSE otherwise.
* *
...@@ -78,7 +79,7 @@ ...@@ -78,7 +79,7 @@
* @see \Drupal\Core\Theme\ThemeAccessCheck::checkAccess(). * @see \Drupal\Core\Theme\ThemeAccessCheck::checkAccess().
*/ */
function drupal_theme_access($theme) { function drupal_theme_access($theme) {
if (is_object($theme)) { if ($theme instanceof Extension) {
$theme = $theme->name; $theme = $theme->name;
} }
return \Drupal::service('access_check.theme')->checkAccess($theme); return \Drupal::service('access_check.theme')->checkAccess($theme);
...@@ -121,19 +122,9 @@ function drupal_theme_initialize() { ...@@ -121,19 +122,9 @@ function drupal_theme_initialize() {
* *
* This function is useful to initialize a theme when no database is present. * This function is useful to initialize a theme when no database is present.
* *
* @param $theme * @param \Drupal\Core\Extension\Extension $theme
* An object with the following information: * The theme extension object.
* filename * @param \Drupal\Core\Extension\Extension[] $base_theme
* 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
* An optional array of objects that represent the 'base theme' if the * An optional array of objects that represent the 'base theme' if the
* theme is meant to be derivative of another theme. It requires * theme is meant to be derivative of another theme. It requires
* the same information as the $theme object. It should be in * the same information as the $theme object. It should be in
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
namespace Drupal\Core\Config; namespace Drupal\Core\Config;
use Drupal\Core\Extension\ExtensionDiscovery;
/** /**
* Storage controller used by the Drupal installer. * Storage controller used by the Drupal installer.
* *
...@@ -110,9 +112,14 @@ public function listAll($prefix = '') { ...@@ -110,9 +112,14 @@ public function listAll($prefix = '') {
*/ */
protected function getAllFolders() { protected function getAllFolders() {
if (!isset($this->folders)) { if (!isset($this->folders)) {
$this->folders = $this->getComponentNames('profile', array(drupal_get_profile())); $this->folders = array();
$this->folders += $this->getComponentNames('module', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0))); // @todo Refactor getComponentNames() to use the extension list directly.
$this->folders += $this->getComponentNames('theme', array_keys(drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'themes'))); 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; return $this->folders;
} }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\YamlFileLoader; use Drupal\Core\DependencyInjection\YamlFileLoader;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Language\Language; use Drupal\Core\Language\Language;
use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
...@@ -83,7 +84,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { ...@@ -83,7 +84,7 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* An array of module data objects. * An array of module data objects.
* *
* The data objects have the same data structure as returned by * 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 * @var array
*/ */
...@@ -297,19 +298,28 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ ...@@ -297,19 +298,28 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ
protected function moduleData($module) { protected function moduleData($module) {
if (!$this->moduleData) { if (!$this->moduleData) {
// First, find profiles. // First, find profiles.
$profiles_scanner = new SystemListing(); $listing = new ExtensionDiscovery();
$all_profiles = $profiles_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles'); $listing->setProfileDirectories(array());
$profiles = array_keys(array_intersect_key($this->moduleList, $all_profiles)); $all_profiles = $listing->scan('profile');
$profiles = array_intersect_key($all_profiles, $this->moduleList);
// If a module is within a profile directory but specifies another // If a module is within a profile directory but specifies another
// profile for testing, it needs to be found in the parent profile. // 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 // In case both profile directories contain the same extension, the
// actual profile always has precedence. // 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. // Now find modules.
$modules_scanner = new SystemListing($profiles); $this->moduleData = $profiles + $listing->scan('module');
$this->moduleData = $all_profiles + $modules_scanner->scan('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules');
} }
return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE; return isset($this->moduleData[$module]) ? $this->moduleData[$module] : FALSE;
} }
......
<?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