Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / drush / drush / src / Boot / BootstrapManager.php
1 <?php
2
3 namespace Drush\Boot;
4
5 use Consolidation\AnnotatedCommand\AnnotationData;
6 use Robo\Common\ConfigAwareTrait;
7 use DrupalFinder\DrupalFinder;
8 use Drush\Log\LogLevel;
9 use Psr\Log\LoggerAwareInterface;
10 use Psr\Log\LoggerAwareTrait;
11 use Robo\Contract\ConfigAwareInterface;
12
13 class BootstrapManager implements LoggerAwareInterface, AutoloaderAwareInterface, ConfigAwareInterface
14 {
15     use LoggerAwareTrait;
16     use AutoloaderAwareTrait;
17     use ConfigAwareTrait;
18
19     /**
20      * @var DrupalFinder
21      */
22     protected $drupalFinder;
23
24     /**
25      * @var \Drush\Boot\Boot[]
26      */
27     protected $bootstrapCandidates = [];
28
29     /**
30      * @var \Drush\Boot\Boot
31      */
32     protected $defaultBootstrapObject;
33
34     /**
35      * @var \Drush\Boot\Boot
36      */
37     protected $bootstrap;
38
39     /**
40      * @var string
41      */
42     protected $root;
43
44     /**
45      * @var string
46      */
47     protected $uri;
48
49     /**
50      * Constructor.
51      *
52      * @param \Drush\Boot\Boot
53      *   The default bootstrap object to use when there are
54      *   no viable candidates to use (e.g. no selected site)
55      */
56     public function __construct(Boot $default)
57     {
58         $this->defaultBootstrapObject = $default;
59
60         // Reset our bootstrap phase to the beginning
61         drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE);
62     }
63
64     /**
65      * Add a bootstrap object to the list of candidates
66      *
67      * @param \Drush\Boot\Boot|Array
68      *   List of boot candidates
69      */
70     public function add($candidateList)
71     {
72         foreach (func_get_args() as $candidate) {
73             $this->bootstrapCandidates[] = $candidate;
74         }
75     }
76
77     public function drupalFinder()
78     {
79         if (!isset($this->drupalFinder)) {
80             $this->drupalFinder = new DrupalFinder();
81         }
82         return $this->drupalFinder;
83     }
84
85     public function setDrupalFinder(DrupalFinder $drupalFinder)
86     {
87         $this->drupalFinder = $drupalFinder;
88     }
89
90     /**
91      * Return the framework root selected by the user.
92      */
93     public function getRoot()
94     {
95         return $this->drupalFinder()->getDrupalRoot();
96     }
97
98     /**
99      * Return the composer root for the selected Drupal site.
100      */
101     public function getComposerRoot()
102     {
103         return $this->drupalFinder()->getComposerRoot();
104     }
105
106     public function locateRoot($root, $start_path = null)
107     {
108         // TODO: Throw if we already bootstrapped a framework?
109
110         if (!isset($root)) {
111             $root = $this->getConfig()->cwd();
112         }
113         if (!$this->drupalFinder()->locateRoot($root)) {
114             //    echo ' Drush must be executed within a Drupal site.'. PHP_EOL;
115             //    exit(1);
116         }
117     }
118
119     /**
120      * Return the framework uri selected by the user.
121      */
122     public function getUri()
123     {
124         return $this->uri;
125     }
126
127     /**
128      * This method is called by the Application iff the user
129      * did not explicitly provide a URI.
130      */
131     public function selectUri($cwd)
132     {
133         $uri = $this->bootstrap()->findUri($this->getRoot(), $cwd);
134         $this->setUri($uri);
135         return $uri;
136     }
137
138     public function setUri($uri)
139     {
140         // TODO: Throw if we already bootstrapped a framework?
141         // n.b. site-install needs to set the uri.
142         $this->uri = $uri;
143         if ($this->bootstrap) {
144             $this->bootstrap->setUri($this->getUri());
145         }
146     }
147
148     /**
149      * Return the bootstrap object in use.  This will
150      * be the latched bootstrap object if we have started
151      * bootstrapping; otherwise, it will be whichever bootstrap
152      * object is best for the selected root.
153      *
154      * @return \Drush\Boot\Boot
155      */
156     public function bootstrap()
157     {
158         if ($this->bootstrap) {
159             return $this->bootstrap;
160         }
161         return $this->selectBootstrapClass();
162     }
163
164     /**
165      * Look up the best bootstrap class for the given location
166      * from the set of available candidates.
167      *
168      * @return \Drush\Boot\Boot
169      */
170     public function bootstrapObjectForRoot($path)
171     {
172         foreach ($this->bootstrapCandidates as $candidate) {
173             if ($candidate->validRoot($path)) {
174                 // This is not necessary when the autoloader is inflected
175                 // TODO: The autoloader is inflected in the symfony dispatch, but not the traditional Drush dispatcher
176                 if ($candidate instanceof AutoloaderAwareInterface) {
177                     $candidate->setAutoloader($this->autoloader());
178                 }
179                 $candidate->setUri($this->getUri());
180                 return $candidate;
181             }
182         }
183         return null;
184     }
185
186     /**
187      * Select the bootstrap class to use.  If this is called multiple
188      * times, the bootstrap class returned might change on subsequent
189      * calls, if the root directory changes.  Once the bootstrap object
190      * starts changing the state of the system, however, it will
191      * be 'latched', and further calls to Drush::bootstrapf()
192      * will always return the same object.
193      */
194     protected function selectBootstrapClass()
195     {
196         // Once we have selected a Drupal root, we will reduce our bootstrap
197         // candidates down to just the one used to select this site root.
198         $bootstrap = $this->bootstrapObjectForRoot($this->getRoot());
199         // If we have not found a bootstrap class by this point,
200         // then return our default bootstrap object.  The default bootstrap object
201         // should pass through all calls without doing anything that
202         // changes state in a CMS-specific way.
203         if ($bootstrap == null) {
204             $bootstrap = $this->defaultBootstrapObject;
205         }
206
207         return $bootstrap;
208     }
209
210     /**
211      * Once bootstrapping has started, we stash the bootstrap
212      * object being used, and do not allow it to change any
213      * longer.
214      */
215     public function latch($bootstrap)
216     {
217         $this->bootstrap = $bootstrap;
218     }
219
220     /**
221      * Returns an array that determines what bootstrap phases
222      * are necessary to bootstrap the CMS.
223      *
224      * @param bool $function_names
225      *   (optional) If TRUE, return an array of method names index by their
226      *   corresponding phase values. Otherwise return an array of phase values.
227      *
228      * @return array
229      *
230      * @see \Drush\Boot\Boot::bootstrapPhases()
231      */
232     public function bootstrapPhases($function_names = false)
233     {
234         $result = [];
235
236         if ($bootstrap = $this->bootstrap()) {
237             $result = $bootstrap->bootstrapPhases();
238             if (!$function_names) {
239                 $result = array_keys($result);
240             }
241         }
242         return $result;
243     }
244
245     /**
246      * Bootstrap Drush to the desired phase.
247      *
248      * This function will sequentially bootstrap each
249      * lower phase up to the phase that has been requested.
250      *
251      * @param int $phase
252      *   The bootstrap phase to bootstrap to.
253      * @param int $phase_max
254      *   (optional) The maximum level to boot to. This does not have a use in this
255      *   function itself but can be useful for other code called from within this
256      *   function, to know if e.g. a caller is in the process of booting to the
257      *   specified level. If specified, it should never be lower than $phase.
258      * @param \Consolidation\AnnotatedCommand\AnnotationData $annotationData
259      *   Optional annotation data from the command.
260      *
261      * @return bool
262      *   TRUE if the specified bootstrap phase has completed.
263      *
264      * @see \Drush\Boot\Boot::bootstrapPhases()
265      */
266     public function doBootstrap($phase, $phase_max = false, AnnotationData $annotationData = null)
267     {
268         $bootstrap = $this->bootstrap();
269         $phases = $this->bootstrapPhases(true);
270         $result = true;
271
272         // If the requested phase does not exist in the list of available
273         // phases, it means that the command requires bootstrap to a certain
274         // level, but no site root could be found.
275         if (!isset($phases[$phase])) {
276             $result = drush_bootstrap_error('DRUSH_NO_SITE', dt("We could not find an applicable site for that command."));
277         }
278
279         // Once we start bootstrapping past the DRUSH_BOOTSTRAP_DRUSH phase, we
280         // will latch the bootstrap object, and prevent it from changing.
281         if ($phase > DRUSH_BOOTSTRAP_DRUSH) {
282             $this->latch($bootstrap);
283         }
284
285         drush_set_context('DRUSH_BOOTSTRAPPING', true);
286         foreach ($phases as $phase_index => $current_phase) {
287             $bootstrapped_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE', -1);
288             if ($phase_index > $phase) {
289                 break;
290             }
291             if ($phase_index > $bootstrapped_phase) {
292                 if ($result = $this->bootstrapValidate($phase_index)) {
293                     if (method_exists($bootstrap, $current_phase) && !drush_get_error()) {
294                         $this->logger->log(LogLevel::BOOTSTRAP, 'Drush bootstrap phase: {function}()', ['function' => $current_phase]);
295                         $bootstrap->{$current_phase}($annotationData);
296                     }
297                     drush_set_context('DRUSH_BOOTSTRAP_PHASE', $phase_index);
298                 }
299             }
300         }
301         drush_set_context('DRUSH_BOOTSTRAPPING', false);
302         if (!$result || drush_get_error()) {
303             $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', []);
304             foreach ($errors as $code => $message) {
305                 drush_set_error($code, $message);
306             }
307         }
308         return !drush_get_error();
309     }
310
311     /**
312      * Determine whether a given bootstrap phase has been completed
313      *
314      * This function name has a typo which makes me laugh so we choose not to
315      * fix it. Take a deep breath, and smile. See
316      * http://en.wikipedia.org/wiki/HTTP_referer
317      *
318      *
319      * @param int $phase
320      *   The bootstrap phase to test
321      *
322      * @return bool
323      *   TRUE if the specified bootstrap phase has completed.
324      */
325     public function hasBootstrapped($phase)
326     {
327         $phase_index = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
328
329         return isset($phase_index) && ($phase_index >= $phase);
330     }
331
332     /**
333      * Validate whether a bootstrap phase can be reached.
334      *
335      * This function will validate the settings that will be used
336      * during the actual bootstrap process, and allow commands to
337      * progressively bootstrap to the highest level that can be reached.
338      *
339      * This function will only run the validation function once, and
340      * store the result from that execution in a local static. This avoids
341      * validating phases multiple times.
342      *
343      * @param int $phase
344      *   The bootstrap phase to validate to.
345      *
346      * @return bool
347      *   TRUE if bootstrap is possible, FALSE if the validation failed.
348      *
349      * @see \Drush\Boot\Boot::bootstrapPhases()
350      */
351     public function bootstrapValidate($phase)
352     {
353         $bootstrap = $this->bootstrap();
354         $phases = $this->bootstrapPhases(true);
355         static $result_cache = [];
356
357         if (!array_key_exists($phase, $result_cache)) {
358             drush_set_context('DRUSH_BOOTSTRAP_ERRORS', []);
359             drush_set_context('DRUSH_BOOTSTRAP_VALUES', []);
360
361             foreach ($phases as $phase_index => $current_phase) {
362                 $validated_phase = drush_get_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', -1);
363                 if ($phase_index > $phase) {
364                     break;
365                 }
366                 if ($phase_index > $validated_phase) {
367                     $current_phase .= 'Validate';
368                     if (method_exists($bootstrap, $current_phase)) {
369                         $result_cache[$phase_index] = $bootstrap->{$current_phase}();
370                     } else {
371                         $result_cache[$phase_index] = true;
372                     }
373                     drush_set_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', $phase_index);
374                 }
375             }
376         }
377         return $result_cache[$phase];
378     }
379
380     /**
381      * Bootstrap to the specified phase.
382      *
383      * @param string $bootstrapPhase
384      *   Name of phase to bootstrap to. Will be converted to appropriate index.
385      * @param \Consolidation\AnnotatedCommand\AnnotationData $annotationData
386      *   Optional annotation data from the command.
387      *
388      * @return bool
389      *   TRUE if the specified bootstrap phase has completed.
390      *
391      * @throws \Exception
392      *   Thrown when an unknown bootstrap phase is passed in the annotation
393      *   data.
394      */
395     public function bootstrapToPhase($bootstrapPhase, AnnotationData $annotationData = null)
396     {
397         $this->logger->log(LogLevel::BOOTSTRAP, 'Bootstrap to {phase}', ['phase' => $bootstrapPhase]);
398         $phase = $this->bootstrap()->lookUpPhaseIndex($bootstrapPhase);
399         if (!isset($phase)) {
400             throw new \Exception(dt('Bootstrap phase !phase unknown.', ['!phase' => $bootstrapPhase]));
401         }
402         // Do not attempt to bootstrap to a phase that is unknown to the selected bootstrap object.
403         $phases = $this->bootstrapPhases();
404         if (!array_key_exists($phase, $phases) && ($phase >= 0)) {
405             return false;
406         }
407         return $this->bootstrapToPhaseIndex($phase, $annotationData);
408     }
409
410     protected function maxPhaseLimit($bootstrap_str)
411     {
412         $bootstrap_words = explode(' ', $bootstrap_str);
413         array_shift($bootstrap_words);
414         if (empty($bootstrap_words)) {
415             return null;
416         }
417         $stop_phase_name = array_shift($bootstrap_words);
418         return $this->bootstrap()->lookUpPhaseIndex($stop_phase_name);
419     }
420
421     /**
422      * Bootstrap to the specified phase.
423      *
424      * @param int $max_phase_index
425      *   Only attempt bootstrap to the specified level.
426      * @param \Consolidation\AnnotatedCommand\AnnotationData $annotationData
427      *   Optional annotation data from the command.
428      *
429      * @return bool
430      *   TRUE if the specified bootstrap phase has completed.
431      */
432     public function bootstrapToPhaseIndex($max_phase_index, AnnotationData $annotationData = null)
433     {
434         if ($max_phase_index == DRUSH_BOOTSTRAP_MAX) {
435             // Try get a max phase.
436             $bootstrap_str = $annotationData->get('bootstrap');
437             $stop_phase = $this->maxPhaseLimit($bootstrap_str);
438             $this->bootstrapMax($stop_phase);
439             return true;
440         }
441
442         $this->logger->log(LogLevel::BOOTSTRAP, 'Drush bootstrap phase {phase}', ['phase' => $max_phase_index]);
443         $phases = $this->bootstrapPhases();
444         $result = true;
445
446           // Try to bootstrap to the maximum possible level, without generating errors
447         foreach ($phases as $phase_index) {
448             if ($phase_index > $max_phase_index) {
449                 // Stop trying, since we achieved what was specified.
450                 break;
451             }
452
453             $this->logger->log(LogLevel::BOOTSTRAP, 'Try to validate bootstrap phase {phase}', ['phase' => $max_phase_index]);
454
455             if ($this->bootstrapValidate($phase_index)) {
456                 if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE)) {
457                     $this->logger->log(LogLevel::BOOTSTRAP, 'Try to bootstrap at phase {phase}', ['phase' => $max_phase_index]);
458                     $result = $this->doBootstrap($phase_index, $max_phase_index, $annotationData);
459                 }
460             } else {
461                 $this->logger->log(LogLevel::BOOTSTRAP, 'Could not bootstrap at phase {phase}', ['phase' => $max_phase_index]);
462                 $result = false;
463                 break;
464             }
465         }
466
467         return $result;
468     }
469
470     /**
471      * Bootstrap to the highest level possible, without triggering any errors.
472      *
473      * @param int $max_phase_index
474      *   (optional) Only attempt bootstrap to the specified level.
475      * @param \Consolidation\AnnotatedCommand\AnnotationData $annotationData
476      *   Optional annotation data from the command.
477      *
478      * @return int
479      *   The maximum phase to which we bootstrapped.
480      */
481     public function bootstrapMax($max_phase_index = false, AnnotationData $annotationData = null)
482     {
483         // Bootstrap as far as we can without throwing an error, but log for
484         // debugging purposes.
485
486         $phases = $this->bootstrapPhases(true);
487         if (!$max_phase_index) {
488             $max_phase_index = count($phases);
489         }
490
491         if ($max_phase_index >= count($phases)) {
492             $this->logger->log(LogLevel::DEBUG, 'Trying to bootstrap as far as we can');
493         }
494
495         // Try to bootstrap to the maximum possible level, without generating errors.
496         foreach ($phases as $phase_index => $current_phase) {
497             if ($phase_index > $max_phase_index) {
498                 // Stop trying, since we achieved what was specified.
499                 break;
500             }
501
502             if ($this->bootstrapValidate($phase_index)) {
503                 if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) {
504                     $this->doBootstrap($phase_index, $max_phase_index, $annotationData);
505                 }
506             } else {
507                 // $this->bootstrapValidate() only logs successful validations. For us,
508                 // knowing what failed can also be important.
509                 $previous = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
510                 $this->logger->log(LogLevel::DEBUG, 'Bootstrap phase {function}() failed to validate; continuing at {current}()', ['function' => $current_phase, 'current' => $phases[$previous]]);
511                 break;
512             }
513         }
514
515         return drush_get_context('DRUSH_BOOTSTRAP_PHASE');
516     }
517 }