Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / vendor / drush / drush / src / Config / ConfigLocator.php
1 <?php
2 namespace Drush\Config;
3
4 use Consolidation\Config\Loader\ConfigLoaderInterface;
5 use Drush\Config\Loader\YamlConfigLoader;
6 use Consolidation\Config\Loader\ConfigProcessor;
7 use Consolidation\Config\Util\EnvConfig;
8
9 /**
10  * Locate Drush configuration files and load them into the configuration
11  * instance.
12  *
13  * This class knows how to find all of the global and site-local
14  * configuration files for Drush, as long as it is provided with
15  * the necessary base directories:
16  *
17  * - The user's home directory
18  * - The values provided for --config and --alias-path
19  * - The Drupal root
20  *
21  * There are two operating modes that are supported:
22  *
23  * - Normal: All config locations are used.
24  * - Local:  The global locations are omitted.
25  *
26  * The mode is set via the `setLocal()` method.
27  */
28 class ConfigLocator
29 {
30     /**
31      * @var \Robo\Config
32      */
33     protected $config;
34
35     protected $isLocal;
36
37     protected $sources = false;
38
39     protected $siteRoots = [];
40
41     protected $composerRoot;
42
43     protected $configFilePaths = [];
44
45     protected $configFileVariant;
46
47     protected $processedConfigPaths = [];
48
49     /*
50      * From context.inc:
51      *
52      *   Specified by the script itself :
53      *     process  : Generated in the current process.
54      *     cli      : Passed as --option=value to the command line.
55      *     stdin    : Passed as a JSON encoded string through stdin.
56      *     specific : Defined in a command-specific option record, and
57      *                set in the command context whenever that command is used.
58      *     alias    : Defined in an alias record, and set in the
59      *                alias context whenever that alias is used.
60      *
61      *   Specified by config files :
62      *     custom   : Loaded from the config file specified by --config or -c
63      *     site     : Loaded from the drush.yml file in the Drupal site directory.
64      *     drupal   : Loaded from the drush.yml file in the Drupal root directory.
65      *     user     : Loaded from the drush.yml file in the user's home directory.
66      *     home.drush Loaded from the drush.yml file in the $HOME/.drush directory.
67      *     system   : Loaded from the drush.yml file in the system's $PREFIX/etc/drush directory.
68      *     drush    : Loaded from the drush.yml file in the same directory as drush.php.
69      *
70      *   Specified by the script, but has the lowest priority :
71      *     default  : The script might provide some sensible defaults during init.
72      */
73
74     // 'process' context is provided by ConfigOverlay
75     const ENVIRONMENT_CONTEXT = 'environment'; // This is more of a 'runtime' context
76     const PREFLIGHT_CONTEXT = 'cli';
77     // 'stdin' context not implemented
78     // 'specific' context obsolete; command-specific options handled differently by annotated command library
79     const ALIAS_CONTEXT = 'alias';
80     // custom context is obsolete (loaded in USER_CONTEXT)
81     const SITE_CONTEXT = 'site';
82     const DRUPAL_CONTEXT = 'drupal';
83     const USER_CONTEXT = 'user';
84     // home.drush is obsolete (loaded in USER_CONTEXT)
85     // system context is obsolete (loaded in USER_CONTEXT - note priority change)
86     const ENV_CONTEXT = 'env';
87     const DRUSH_CONTEXT = 'drush';
88
89     // 'default' context is provided by ConfigOverlay
90
91     /**
92      * ConfigLocator constructor
93      */
94     public function __construct($envPrefix = '', $configFileVariant = '')
95     {
96         $this->configFileVariant = $configFileVariant;
97         $this->config = new DrushConfig();
98
99         // Add placeholders to establish priority. We add
100         // contexts from lowest to highest priority.
101         $this->config->addPlaceholder(self::DRUSH_CONTEXT);
102         if (!empty($envPrefix)) {
103             $envConfig = new EnvConfig($envPrefix);
104             $this->config->addContext(self::ENV_CONTEXT, $envConfig);
105         }
106         $this->config->addPlaceholder(self::USER_CONTEXT);
107         $this->config->addPlaceholder(self::DRUPAL_CONTEXT);
108         $this->config->addPlaceholder(self::SITE_CONTEXT); // not implemented yet (multisite)
109         $this->config->addPlaceholder(self::ALIAS_CONTEXT);
110         $this->config->addPlaceholder(self::PREFLIGHT_CONTEXT);
111         $this->config->addPlaceholder(self::ENVIRONMENT_CONTEXT);
112
113         $this->isLocal = false;
114
115         $this->configFilePaths = [];
116     }
117
118     /**
119      * Put the config locator into 'local 'mode.
120      *
121      * @param bool $isLocal
122      */
123     public function setLocal($isLocal)
124     {
125         $this->isLocal = $isLocal;
126     }
127
128     /**
129      * Keep track of the source that every config item originally came from.
130      * Potentially useful in debugging.  If collectSources(true) is called,
131      * then the sources will be accumulated as config files are loaded. Otherwise,
132      * this information will not be saved.
133      *
134      * @param bool $collect
135      * @return $this
136      */
137     public function collectSources($collect = true)
138     {
139         $this->sources = $collect ? [] : false;
140         return $this;
141     }
142
143     /**
144      * Return all of the sources for every configuration item. The key
145      * is the address of the configuration item, and the value is the
146      * configuration file it was loaded from. Note that this method will
147      * return just an empty array unless collectSources(true) is called
148      * prior to loading configuration files.
149      *
150      * @return array
151      */
152     public function sources()
153     {
154         return $this->sources;
155     }
156
157     /**
158      * Return a list of all configuration files that were loaded.
159      *
160      * @return string[]
161      */
162     public function configFilePaths()
163     {
164         return $this->configFilePaths;
165     }
166
167     /**
168      * Accumulate the sources provided by the configuration loader.
169      */
170     protected function addToSources(array $sources)
171     {
172         if (!is_array($this->sources)) {
173             return;
174         }
175         $this->sources = array_merge_recursive($this->sources, $sources);
176     }
177
178     /**
179      * Return the configuration object. Create it and load it with
180      * all identified configuration if necessary.
181      *
182      * @return Config
183      */
184     public function config()
185     {
186         return $this->config;
187     }
188
189     /**
190      * Exports all of the information stored in the environment, and adds
191      * it to the configuration.  The Environment object itself is only
192      * available during preflight; the information exported here may be
193      * obtained by commands et. al. as needed. @see Environment::exportConfigData()
194      *
195      * @param Environment $environent
196      * @return $this
197      */
198     public function addEnvironment(Environment $environment)
199     {
200         $this->config->getContext(self::ENVIRONMENT_CONTEXT)->import($environment->exportConfigData());
201         return $this;
202     }
203
204     /**
205      *  Add config paths defined in preflight configuration.
206      *
207      * @param array $paths
208      * @return $this
209      */
210     public function addPreflightConfigFiles($filepaths)
211     {
212         $this->addConfigPaths(self::PREFLIGHT_CONTEXT, (array) $filepaths);
213         return $this;
214     }
215
216     /**
217      * Take any configuration from the active alias record, and add it
218      * to our configuratino.
219      * @return $this
220      */
221     public function addAliasConfig($aliasConfig)
222     {
223         $this->config->addContext(self::ALIAS_CONTEXT, $aliasConfig);
224         return $this;
225     }
226
227
228     /**
229      * Given the path provided via --config and the user's home directory,
230      * add all of the user configuration paths.
231      *
232      * In 'local' mode, only the --config location is used.
233      * @return $this
234      */
235     public function addUserConfig($configPaths, $systemConfigPath, $userConfigDir)
236     {
237         $paths = $configPaths;
238         if (!$this->isLocal) {
239             $paths = array_merge($paths, [ $systemConfigPath, $userConfigDir ]);
240         }
241         $this->addConfigPaths(self::USER_CONTEXT, $paths);
242         return $this;
243     }
244
245     /**
246      * Add the Drush project directory as a configuration search location.
247      *
248      * @param $drushProjectDir path to the drush project directory
249      * @return $this
250      */
251     public function addDrushConfig($drushProjectDir)
252     {
253         if (!$this->isLocal) {
254             $this->addConfigPaths(self::DRUSH_CONTEXT, [ $drushProjectDir ]);
255         }
256         return $this;
257     }
258
259     /**
260      * Add any configuration files found around the Drupal root of the
261      * selected site.
262      *
263      * @param Path to the selected Drupal site
264      * @return $this
265      */
266     public function addSitewideConfig($siteRoot)
267     {
268         // There might not be a site.
269         if (!is_dir($siteRoot)) {
270             return;
271         }
272
273         // We might have already processed this root.
274         $siteRoot = realpath($siteRoot);
275         if (in_array($siteRoot, $this->siteRoots)) {
276             return;
277         }
278
279         // Remember that we've seen this location.
280         $this->siteRoots[] = $siteRoot;
281
282         $this->addConfigPaths(self::DRUPAL_CONTEXT, [ dirname($siteRoot) . '/drush', "$siteRoot/drush", "$siteRoot/sites/all/drush" ]);
283         return $this;
284     }
285
286     /**
287      * Add any configuration file found at any of the provided paths. Both the
288      * provided location, and the directory `config` inside each provided location
289      * is searched for a drush.yml file.
290      *
291      * @param string $contextName Which context to put all configuration files in.
292      * @param string[] $paths List of paths to search for configuration.
293      * @return $this
294      */
295     public function addConfigPaths($contextName, $paths)
296     {
297         $loader = new YamlConfigLoader();
298         // Make all of the config values parsed so far available in evaluations.
299         $reference = $this->config()->export();
300         $processor = new ConfigProcessor();
301         $processor->useMergeStrategyForKeys(['drush.paths.include', 'drush.paths.alias-path']);
302         $context = $this->config->getContext($contextName);
303         $processor->add($context->export());
304
305         $candidates = [
306             'drush.yml',
307         ];
308         if ($this->configFileVariant) {
309             $candidates[] = "drush{$this->configFileVariant}.yml";
310         }
311         $candidates = $this->expandCandidates($candidates, 'config/');
312         $config_files = $this->findConfigFiles($paths, $candidates);
313         $this->addConfigFiles($processor, $loader, $config_files);
314
315         // Complete config import.
316         $this->addToSources($processor->sources());
317         $context->import($processor->export($reference));
318         $this->config->addContext($contextName, $context);
319         $this->processedConfigPaths = array_merge($this->processedConfigPaths, $paths);
320
321         // Recursive case.
322         if ($context->has('drush.paths.config')) {
323             $new_config_paths = array_diff((array) $context->get('drush.paths.config'), $this->processedConfigPaths);
324             if ($new_config_paths) {
325                 $this->addConfigPaths($contextName, $new_config_paths);
326             }
327         }
328
329         return $this;
330     }
331
332     /**
333      * Adds $configFiles config files.
334      *
335      * @param ConfigProcessor $processor
336      * @param ConfigLoaderInterface $loader
337      * @param array $configFiles
338      */
339     protected function addConfigFiles(ConfigProcessor $processor, ConfigLoaderInterface $loader, array $configFiles)
340     {
341         foreach ($configFiles as $configFile) {
342             $processor->extend($loader->load($configFile));
343             $this->configFilePaths[] = $configFile;
344         }
345     }
346
347     /**
348      * Given a list of paths, and candidates that might exist at each path,
349      * return all of the candidates that can be found. Candidates may be
350      * either directories or files.
351      *
352      * @param string[] $paths
353      * @param string[] $candidates
354      * @return string[] paths
355      */
356     protected function identifyCandidates($paths, $candidates)
357     {
358         $configFiles = [];
359         foreach ($paths as $path) {
360             $configFiles = array_merge($configFiles, $this->identifyCandidatesAtPath($path, $candidates));
361         }
362         return $configFiles;
363     }
364
365     /**
366      * Search for all matching candidate locations at a single path.
367      * Candidate locations may be either directories or files.
368      *
369      * @param string $path
370      * @param string[] $candidates
371      * @return string[]
372      */
373     protected function identifyCandidatesAtPath($path, $candidates)
374     {
375         if (!is_dir($path)) {
376             return [];
377         }
378
379         $result = [];
380         foreach ($candidates as $candidate) {
381             $configFile = empty($candidate) ? $path : "$path/$candidate";
382             if (file_exists($configFile)) {
383                 $result[] = $configFile;
384             }
385         }
386         return $result;
387     }
388
389     /**
390      * Get the site aliases according to preflight arguments and environment.
391      *
392      * @param $preflightArgs
393      * @param Environment $environment
394      *
395      * @return array
396      */
397     public function getSiteAliasPaths($paths, Environment $environment)
398     {
399         // In addition to the paths passed in to us (from --alias-paths
400         // commandline options), add some site-local locations.
401         $base_dirs = array_filter(array_merge($this->siteRoots, [$this->composerRoot]));
402         $site_local_paths = array_map(
403             function ($item) {
404                 return "$item/drush/sites";
405             },
406             $base_dirs
407         );
408         $paths = array_merge($paths, $site_local_paths);
409
410         return $paths;
411     }
412
413     /**
414      * Get the commandfile paths according to preflight arguments.
415      *
416      * @param $preflightArgs
417      *
418      * @return array
419      */
420     public function getCommandFilePaths($commandPaths, $root)
421     {
422         $builtin = $this->getBuiltinCommandFilePaths();
423         $included = $this->getIncludedCommandFilePaths($commandPaths);
424         $site = $this->getSiteCommandFilePaths($root);
425
426         return array_merge(
427             $builtin,
428             $included,
429             $site
430         );
431     }
432
433     /**
434      * Return all of the built-in commandfile locations
435      */
436     protected function getBuiltinCommandFilePaths()
437     {
438         return [
439             dirname(__DIR__),
440         ];
441     }
442
443     /**
444      * Return all of the commandfile locations specified via
445      * an 'include' option.
446      */
447     protected function getIncludedCommandFilePaths($commandPaths)
448     {
449         $searchpath = [];
450         // Commands specified by 'include' option
451         foreach ($commandPaths as $commandPath) {
452             if (is_dir($commandPath)) {
453                 $searchpath[] = $commandPath;
454             }
455         }
456         return $searchpath;
457     }
458
459     /**
460      * Return all of the commandfile paths in any '$root/drush' or
461      * 'dirname($root)/drush' directory that contains a composer.json
462      * file or a 'Commands' or 'src/Commands' directory.
463      */
464     protected function getSiteCommandFilePaths($root)
465     {
466         $directories = ["$root/drush", dirname($root) . '/drush', "$root/sites/all/drush"];
467
468         return array_filter($directories, 'is_dir');
469     }
470
471     /**
472      * Sets the composer root.
473      *
474      * @param $selectedComposerRoot
475      */
476     public function setComposerRoot($selectedComposerRoot)
477     {
478         $this->composerRoot = $selectedComposerRoot;
479     }
480
481     /**
482      * Double the candidates, adding '$prefix' before each existing one.
483      */
484     public function expandCandidates($candidates, $prefix)
485     {
486         $additional = array_map(
487             function ($item) use ($prefix) {
488                 return $prefix . $item;
489             },
490             $candidates
491         );
492         return array_merge($candidates, $additional);
493     }
494
495     /**
496      * Given an array of paths, separates files and directories.
497      *
498      * @param array $paths
499      *   An array of config paths. These may be config files or paths to dirs
500      *   containing config files.
501      * @param array $candidates
502      *   An array filenames that are considered config files.
503      *
504      * @return array
505      *   An array. The first row is an array of files, the second row is an
506      *   array of dirs.
507      */
508     protected function findConfigFiles($paths, $candidates)
509     {
510         $files = [];
511         $dirs = [];
512         foreach ($paths as $path) {
513             if (file_exists($path)) {
514                 if (is_dir($path)) {
515                     $dirs[] = realpath($path);
516                 } else {
517                     $files[] = realpath($path);
518                 }
519             }
520         }
521
522         // Search directories for config file candidates.
523         $discovered_config_files = $this->identifyCandidates($dirs, $candidates);
524
525         // Merge discoverd candidates with explicitly specified config files.
526         $config_files = array_merge($discovered_config_files, $files);
527
528         return $config_files;
529     }
530 }