6 use Drush\Log\LogLevel;
8 use Psr\Log\LoggerInterface;
9 use Drupal\user\Entity\User;
10 use Symfony\Component\HttpFoundation\Request;
11 use Webmozart\PathUtil\Path;
13 abstract class DrupalBoot extends BaseBoot
16 * Select the best URI for the provided cwd. Only called
17 * if the user did not explicitly specify a URI.
19 public function findUri($root, $cwd)
21 if (Path::isBasePath($root, $cwd)) {
22 $siteDir = $this->scanUpForUri($root, $cwd);
24 return basename($siteDir);
30 protected function scanUpForUri($root, $scan)
32 $root = Path::canonicalize($root);
33 while (!empty($scan)) {
34 if (file_exists("$scan/settings.php")) {
37 // Use Path::getDirectory instead of dirname to
38 // avoid certain bugs. Returns a canonicalized path.
39 $next = Path::getDirectory($scan);
51 public function validRoot($path)
55 public function getVersion($drupal_root)
59 public function confPath($require_settings = true, $reset = false)
61 return confPath($require_settings, $reset);
65 * Bootstrap phases used with Drupal:
67 * DRUSH_BOOTSTRAP_DRUSH = Only Drush.
68 * DRUSH_BOOTSTRAP_DRUPAL_ROOT = Find a valid Drupal root.
69 * DRUSH_BOOTSTRAP_DRUPAL_SITE = Find a valid Drupal site.
70 * DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION = Load the site's settings.
71 * DRUSH_BOOTSTRAP_DRUPAL_DATABASE = Initialize the database.
72 * DRUSH_BOOTSTRAP_DRUPAL_FULL = Initialize Drupal fully.
74 * The value is the name of the method of the Boot class to
75 * execute when bootstrapping. Prior to bootstrapping, a "validate"
76 * method is called, if defined. The validate method name is the
77 * bootstrap method name with "_validate" appended.
79 public function bootstrapPhases()
81 return parent::bootstrapPhases() + [
82 DRUSH_BOOTSTRAP_DRUPAL_ROOT => 'bootstrapDrupalRoot',
83 DRUSH_BOOTSTRAP_DRUPAL_SITE => 'bootstrapDrupalSite',
84 DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION => 'bootstrapDrupalConfiguration',
85 DRUSH_BOOTSTRAP_DRUPAL_DATABASE => 'bootstrapDrupalDatabase',
86 DRUSH_BOOTSTRAP_DRUPAL_FULL => 'bootstrapDrupalFull',
90 public function bootstrapPhaseMap()
92 return parent::bootstrapPhaseMap() + [
93 'root' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
94 'site' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
95 'config' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
96 'configuration' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
97 'db' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
98 'database' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
99 'full' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
104 * Validate the DRUSH_BOOTSTRAP_DRUPAL_ROOT phase.
106 * In this function, we will check if a valid Drupal directory is available.
107 * We also determine the value that will be stored in the DRUSH_DRUPAL_ROOT
108 * context and DRUPAL_ROOT constant if it is considered a valid option.
110 public function bootstrapDrupalRootValidate()
112 $drupal_root = Drush::bootstrapManager()->getRoot();
114 if (empty($drupal_root)) {
115 return drush_bootstrap_error('DRUSH_NO_DRUPAL_ROOT', dt("A Drupal installation directory could not be found"));
117 // TODO: Perhaps $drupal_root is now ALWAYS valid by the time we get here.
118 if (!$this->legacyValidRootCheck($drupal_root)) {
119 return drush_bootstrap_error('DRUSH_INVALID_DRUPAL_ROOT', dt("The directory !drupal_root does not contain a valid Drupal installation", ['!drupal_root' => $drupal_root]));
122 $version = drush_drupal_version($drupal_root);
123 $major_version = drush_drupal_major_version($drupal_root);
124 if ($major_version <= 6) {
125 return drush_set_error('DRUSH_DRUPAL_VERSION_UNSUPPORTED', dt('Drush !drush_version does not support Drupal !major_version.', ['!drush_version' => Drush::getMajorVersion(), '!major_version' => $major_version]));
128 drush_bootstrap_value('drupal_root', $drupal_root);
133 protected function legacyValidRootCheck($root)
135 $bootstrap_class = Drush::bootstrapManager()->bootstrapObjectForRoot($root);
136 return $bootstrap_class != null;
140 * Bootstrap Drush with a valid Drupal Directory.
142 * In this function, the pwd will be moved to the root
143 * of the Drupal installation.
145 * We also now load the drush.yml for this specific Drupal site.
146 * We can now include files from the Drupal tree, and figure
147 * out more context about the codebase, such as the version of Drupal.
149 public function bootstrapDrupalRoot()
152 $drupal_root = drush_set_context('DRUSH_DRUPAL_ROOT', drush_bootstrap_value('drupal_root'));
154 $this->logger->log(LogLevel::BOOTSTRAP, dt("Change working directory to !drupal_root", ['!drupal_root' => $drupal_root]));
155 $version = drush_drupal_version();
156 $major_version = drush_drupal_major_version();
158 $core = $this->bootstrapDrupalCore($drupal_root);
160 // DRUSH_DRUPAL_CORE should point to the /core folder in Drupal 8+ or to DRUPAL_ROOT
161 // in prior versions.
162 define('DRUSH_DRUPAL_CORE', $core);
164 $this->logger->log(LogLevel::BOOTSTRAP, dt("Initialized Drupal !version root directory at !drupal_root", ["!version" => $version, '!drupal_root' => $drupal_root]));
168 * VALIDATE the DRUSH_BOOTSTRAP_DRUPAL_SITE phase.
170 * In this function we determine the URL used for the command,
171 * and check for a valid settings.php file.
173 public function bootstrapDrupalSiteValidate()
178 * Called by bootstrapDrupalSite to do the main work
179 * of the drush drupal site bootstrap.
181 public function bootstrapDoDrupalSite()
183 drush_set_context('DRUSH_URI', $this->uri);
184 $site = drush_set_context('DRUSH_DRUPAL_SITE', drush_bootstrap_value('site'));
185 $confPath = drush_set_context('DRUSH_DRUPAL_SITE_ROOT', drush_bootstrap_value('confPath'));
187 $this->logger->log(LogLevel::BOOTSTRAP, dt("Initialized Drupal site !site at !site_root", ['!site' => $site, '!site_root' => $confPath]));
191 * Initialize a site on the Drupal root.
193 * We now set various contexts that we determined and confirmed to be valid.
194 * Additionally we load an optional drush.yml file in the site directory.
196 public function bootstrapDrupalSite()
198 $this->bootstrapDoDrupalSite();
202 * Initialize and load the Drupal configuration files.
204 public function bootstrapDrupalConfiguration()
209 * Validate the DRUSH_BOOTSTRAP_DRUPAL_DATABASE phase
211 * Attempt to make a working database connection using the
212 * database credentials that were loaded during the previous
215 public function bootstrapDrupalDatabaseValidate()
217 // Drupal requires PDO, and Drush requires php 5.6+ which ships with PDO
218 // but PHP may be compiled with --disable-pdo.
219 if (!class_exists('\PDO')) {
220 $this->logger->log(LogLevel::BOOTSTRAP, dt('PDO support is required.'));
224 $sql = SqlBase::create();
225 // Drush requires a database client program during its Drupal bootstrap.
226 $command = $sql->command();
227 if (drush_program_exists($command) === false) {
228 $this->logger->warning(dt('The command \'!command\' is required for preflight but cannot be found. Please install it and retry.', ['!command' => $command]));
231 if (!$sql->query('SELECT 1;', null, drush_bit_bucket())) {
232 $message = dt("Drush was not able to start (bootstrap) the Drupal database.\n");
233 $message .= dt("Hint: This may occur when Drush is trying to:\n");
234 $message .= dt(" * bootstrap a site that has not been installed or does not have a configured database. In this case you can select another site with a working database setup by specifying the URI to use with the --uri parameter on the command line. See `drush topic docs-aliases` for details.\n");
235 $message .= dt(" * connect the database through a socket. The socket file may be wrong or the php-cli may have no access to it in a jailed shell. See http://drupal.org/node/1428638 for details.\n");
236 $message .= dt('More information may be available by running `drush status`');
237 $this->logger->log(LogLevel::BOOTSTRAP, $message);
240 } catch (\Exception $e) {
241 $this->logger->log(LogLevel::DEBUG, dt('Unable to validate DB: @e', ['@e' => $e->getMessage()]));
248 * Test to see if the Drupal database has a specified
251 * This is a bootstrap helper function designed to be called
252 * from the bootstrapDrupalDatabaseValidate() methods of
253 * derived DrupalBoot classes. If a database exists, but is
254 * empty, then the Drupal database bootstrap will fail. To
255 * prevent this situation, we test for some table that is needed
256 * in an ordinary bootstrap, and return FALSE from the validate
257 * function if it does not exist, so that we do not attempt to
258 * start the database bootstrap.
260 * Note that we must manually do our own prefix testing here,
261 * because the existing wrappers we have for handling prefixes
262 * depend on bootstrapping to the "database" phase, and therefore
263 * are not available to validate this same phase.
265 * @param $required_tables
266 * Array of table names, or string with one table name
268 * @return TRUE if all required tables exist in the database.
270 public function bootstrapDrupalDatabaseHasTable($required_tables)
273 $sql = SqlBase::create();
274 $spec = $sql->getDbSpec();
275 $prefix = isset($spec['prefix']) ? $spec['prefix'] : null;
276 if (!is_array($prefix)) {
277 $prefix = ['default' => $prefix];
279 foreach ((array)$required_tables as $required_table) {
280 $prefix_key = array_key_exists($required_table, $prefix) ? $required_table : 'default';
281 $table_name = $prefix[$prefix_key] . $required_table;
282 if (!$sql->alwaysQuery("SELECT 1 FROM $table_name LIMIT 1;", null, drush_bit_bucket())) {
283 $this->logger->notice('Missing database table: '. $table_name);
287 } catch (Exception $e) {
288 // Usually the checks above should return a result without
289 // throwing an exception, but we'll catch any that are
290 // thrown just in case.
297 * Boostrap the Drupal database.
299 public function bootstrapDrupalDatabase()
301 // We presume that our derived classes will connect and then
302 // either fail, or call us via parent::
303 $this->logger->log(LogLevel::BOOTSTRAP, dt("Successfully connected to the Drupal database."));
307 * Attempt to load the full Drupal system.
309 public function bootstrapDrupalFull()