123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- <?php
- 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.
- * https://www.drupal.org/node/2532228
- */
- 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 = [
- '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 = [
- // 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;
- /**
- * Construct a RecursiveExtensionFilterIterator.
- *
- * @param \RecursiveIterator $iterator
- * The iterator to filter.
- * @param array $blacklist
- * (optional) Add to the blacklist of directories that should be filtered
- * out during the iteration.
- */
- public function __construct(\RecursiveIterator $iterator, array $blacklist = []) {
- parent::__construct($iterator);
- $this->blacklist = array_merge($this->blacklist, $blacklist);
- }
- /**
- * 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';
- }
- }
- /**
- * {@inheritdoc}
- */
- public function getChildren() {
- $filter = parent::getChildren();
- // Pass on the blacklist.
- $filter->blacklist = $this->blacklist;
- // Pass the $acceptTests flag forward to child iterators.
- $filter->acceptTests($this->acceptTests);
- return $filter;
- }
- /**
- * {@inheritdoc}
- */
- 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';
- }
- }
- }
|