Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Core / Extension / Discovery / RecursiveExtensionFilterIterator.php
1 <?php
2
3 namespace Drupal\Core\Extension\Discovery;
4
5 /**
6  * Filters a RecursiveDirectoryIterator to discover extensions.
7  *
8  * To ensure the best possible performance for extension discovery, this
9  * filter implementation hard-codes a range of assumptions about directories
10  * in which Drupal extensions may appear and in which not. Every unnecessary
11  * subdirectory tree recursion is avoided.
12  *
13  * The list of globally ignored directory names is defined in the
14  * RecursiveExtensionFilterIterator::$blacklist property.
15  *
16  * In addition, all 'config' directories are skipped, unless the directory path
17  * ends with 'modules/config', so as to still find the config module provided by
18  * Drupal core and still allow that module to be overridden with a custom config
19  * module.
20  *
21  * Lastly, ExtensionDiscovery instructs this filter to additionally skip all
22  * 'tests' directories at regular runtime, since just with Drupal core only, the
23  * discovery process yields 4x more extensions when tests are not ignored.
24  *
25  * @see ExtensionDiscovery::scan()
26  * @see ExtensionDiscovery::scanDirectory()
27  *
28  * @todo Use RecursiveCallbackFilterIterator instead of the $acceptTests
29  *   parameter forwarding once PHP 5.4 is available.
30  */
31 class RecursiveExtensionFilterIterator extends \RecursiveFilterIterator {
32
33   /**
34    * List of base extension type directory names to scan.
35    *
36    * Only these directory names are considered when starting a filesystem
37    * recursion in a search path.
38    *
39    * @var array
40    */
41   protected $whitelist = [
42     'profiles',
43     'modules',
44     'themes',
45   ];
46
47   /**
48    * List of directory names to skip when recursing.
49    *
50    * These directories are globally ignored in the recursive filesystem scan;
51    * i.e., extensions (of all types) are not able to use any of these names,
52    * because their directory names will be skipped.
53    *
54    * @var array
55    */
56   protected $blacklist = [
57     // Object-oriented code subdirectories.
58     'src',
59     'lib',
60     'vendor',
61     // Front-end.
62     'assets',
63     'css',
64     'files',
65     'images',
66     'js',
67     'misc',
68     'templates',
69     // Legacy subdirectories.
70     'includes',
71     // Test subdirectories.
72     'fixtures',
73     // @todo ./tests/Drupal should be ./tests/src/Drupal
74     'Drupal',
75   ];
76
77   /**
78    * Whether to include test directories when recursing.
79    *
80    * @var bool
81    */
82   protected $acceptTests = FALSE;
83
84   /**
85    * Construct a RecursiveExtensionFilterIterator.
86    *
87    * @param \RecursiveIterator $iterator
88    *   The iterator to filter.
89    * @param array $blacklist
90    *   (optional) Add to the blacklist of directories that should be filtered
91    *   out during the iteration.
92    */
93   public function __construct(\RecursiveIterator $iterator, array $blacklist = []) {
94     parent::__construct($iterator);
95     $this->blacklist = array_merge($this->blacklist, $blacklist);
96   }
97
98   /**
99    * Controls whether test directories will be scanned.
100    *
101    * @param bool $flag
102    *   Pass FALSE to skip all test directories in the discovery. If TRUE,
103    *   extensions in test directories will be discovered and only the global
104    *   directory blacklist in RecursiveExtensionFilterIterator::$blacklist is
105    *   applied.
106    */
107   public function acceptTests($flag = FALSE) {
108     $this->acceptTests = $flag;
109     if (!$this->acceptTests) {
110       $this->blacklist[] = 'tests';
111     }
112   }
113
114   /**
115    * {@inheritdoc}
116    */
117   public function getChildren() {
118     $filter = parent::getChildren();
119     // Pass on the blacklist.
120     $filter->blacklist = $this->blacklist;
121     // Pass the $acceptTests flag forward to child iterators.
122     $filter->acceptTests($this->acceptTests);
123     return $filter;
124   }
125
126   /**
127    * {@inheritdoc}
128    */
129   public function accept() {
130     $name = $this->current()->getFilename();
131     // FilesystemIterator::SKIP_DOTS only skips '.' and '..', but not hidden
132     // directories (like '.git').
133     if ($name[0] == '.') {
134       return FALSE;
135     }
136     if ($this->isDir()) {
137       // If this is a subdirectory of a base search path, only recurse into the
138       // fixed list of expected extension type directory names. Required for
139       // scanning the top-level/root directory; without this condition, we would
140       // recurse into the whole filesystem tree that possibly contains other
141       // files aside from Drupal.
142       if ($this->current()->getSubPath() == '') {
143         return in_array($name, $this->whitelist, TRUE);
144       }
145       // 'config' directories are special-cased here, because every extension
146       // contains one. However, those default configuration directories cannot
147       // contain extensions. The directory name cannot be globally skipped,
148       // because core happens to have a directory of an actual module that is
149       // named 'config'. By explicitly testing for that case, we can skip all
150       // other config directories, and at the same time, still allow the core
151       // config module to be overridden/replaced in a profile/site directory
152       // (whereas it must be located directly in a modules directory).
153       if ($name == 'config') {
154         return substr($this->current()->getPathname(), -14) == 'modules/config';
155       }
156       // Accept the directory unless the name is blacklisted.
157       return !in_array($name, $this->blacklist, TRUE);
158     }
159     else {
160       // Only accept extension info files.
161       return substr($name, -9) == '.info.yml';
162     }
163   }
164
165 }