DrupalBoot.php

  1. 8.0.x lib/Drush/Boot/DrupalBoot.php
  2. 7.x lib/Drush/Boot/DrupalBoot.php
  3. master lib/Drush/Boot/DrupalBoot.php

Namespace

Drush\Boot

Classes

Namesort descending Description
DrupalBoot

File

lib/Drush/Boot/DrupalBoot.php
View source
  1. <?php
  2. namespace Drush\Boot;
  3. use Drush\Log\LogLevel;
  4. abstract class DrupalBoot extends BaseBoot {
  5. function __construct() {
  6. }
  7. function valid_root($path) {
  8. }
  9. function get_profile() {
  10. }
  11. /**
  12. * Bootstrap phases used with Drupal:
  13. *
  14. * DRUSH_BOOTSTRAP_DRUSH = Only Drush.
  15. * DRUSH_BOOTSTRAP_DRUPAL_ROOT = Find a valid Drupal root.
  16. * DRUSH_BOOTSTRAP_DRUPAL_SITE = Find a valid Drupal site.
  17. * DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION = Load the site's settings.
  18. * DRUSH_BOOTSTRAP_DRUPAL_DATABASE = Initialize the database.
  19. * DRUSH_BOOTSTRAP_DRUPAL_FULL = Initialize Drupal fully.
  20. * DRUSH_BOOTSTRAP_DRUPAL_LOGIN = Log into Drupal with a valid user.
  21. *
  22. * The value is the name of the method of the Boot class to
  23. * execute when bootstrapping. Prior to bootstrapping, a "validate"
  24. * method is called, if defined. The validate method name is the
  25. * bootstrap method name with "_validate" appended.
  26. */
  27. function bootstrap_phases() {
  28. return array(
  29. DRUSH_BOOTSTRAP_DRUSH => 'bootstrap_drush',
  30. DRUSH_BOOTSTRAP_DRUPAL_ROOT => 'bootstrap_drupal_root',
  31. DRUSH_BOOTSTRAP_DRUPAL_SITE => 'bootstrap_drupal_site',
  32. DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION => 'bootstrap_drupal_configuration',
  33. DRUSH_BOOTSTRAP_DRUPAL_DATABASE => 'bootstrap_drupal_database',
  34. DRUSH_BOOTSTRAP_DRUPAL_FULL => 'bootstrap_drupal_full',
  35. DRUSH_BOOTSTRAP_DRUPAL_LOGIN => 'bootstrap_drupal_login');
  36. }
  37. /**
  38. * The phases where Drush will look to see if new commandfiles
  39. * have been defined. For Drupal, we first do a preflight, and
  40. * if a command is not found at that point, we next attempt a full
  41. * bootstrap. If a command is found after preflight, then we
  42. * bootstrap to the phase declared by the command.
  43. */
  44. function bootstrap_init_phases() {
  45. return array(DRUSH_BOOTSTRAP_DRUSH, DRUSH_BOOTSTRAP_DRUPAL_FULL);
  46. }
  47. function enforce_requirement(&$command) {
  48. parent::enforce_requirement($command);
  49. $this->drush_enforce_requirement_drupal_dependencies($command);
  50. }
  51. function report_command_error($command) {
  52. // If we reach this point, command doesn't fit requirements or we have not
  53. // found either a valid or matching command.
  54. // If no command was found check if it belongs to a disabled module.
  55. if (!$command) {
  56. $command = $this->drush_command_belongs_to_disabled_module();
  57. }
  58. parent::report_command_error($command);
  59. }
  60. function command_defaults() {
  61. return array(
  62. 'drupal dependencies' => array(),
  63. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
  64. );
  65. }
  66. /**
  67. * @return array of strings - paths to directories where contrib
  68. * modules can be found
  69. */
  70. abstract function contrib_modules_paths();
  71. /**
  72. * @return array of strings - paths to directories where contrib
  73. * themes can be found
  74. */
  75. abstract function contrib_themes_paths();
  76. function commandfile_searchpaths($phase, $phase_max = FALSE) {
  77. if (!$phase_max) {
  78. $phase_max = $phase;
  79. }
  80. $searchpath = array();
  81. switch ($phase) {
  82. case DRUSH_BOOTSTRAP_DRUPAL_ROOT:
  83. $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  84. $searchpath[] = $drupal_root . '/../drush';
  85. $searchpath[] = $drupal_root . '/drush';
  86. $searchpath[] = $drupal_root . '/sites/all/drush';
  87. // Add the drupalboot.drush.inc commandfile.
  88. // $searchpath[] = __DIR__;
  89. break;
  90. case DRUSH_BOOTSTRAP_DRUPAL_SITE:
  91. // If we are going to stop bootstrapping at the site, then
  92. // we will quickly add all commandfiles that we can find for
  93. // any module associated with the site, whether it is enabled
  94. // or not. If we are, however, going to continue on to bootstrap
  95. // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will
  96. // instead wait for that phase, which will more carefully add
  97. // only those Drush commandfiles that are associated with
  98. // enabled modules.
  99. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  100. $searchpath = array_merge($searchpath, $this->contrib_modules_paths());
  101. // Adding commandfiles located within /profiles. Try to limit to one profile for speed. Note
  102. // that Drupal allows enabling modules from a non-active profile so this logic is kinda dodgy.
  103. $cid = drush_cid_install_profile();
  104. if ($cached = drush_cache_get($cid)) {
  105. $profile = $cached->data;
  106. $searchpath[] = "profiles/$profile/modules";
  107. }
  108. else {
  109. // If install_profile is not available, scan all profiles.
  110. $searchpath[] = "profiles";
  111. $searchpath[] = "sites/all/profiles";
  112. }
  113. }
  114. // TODO: Treat themes like modules and stop unconditionally searching here.
  115. $searchpath = array_merge($searchpath, $this->contrib_themes_paths());
  116. break;
  117. case DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION:
  118. // Nothing to do here anymore. Left for documentation.
  119. break;
  120. case DRUSH_BOOTSTRAP_DRUPAL_FULL:
  121. // Add enabled module paths, excluding the install profile. Since we are bootstrapped,
  122. // we can use the Drupal API.
  123. $ignored_modules = drush_get_option_list('ignored-modules', array());
  124. $cid = drush_cid_install_profile();
  125. if ($cached = drush_cache_get($cid)) {
  126. $ignored_modules[] = $cached->data;
  127. }
  128. foreach (array_diff(drush_module_list(), $ignored_modules) as $module) {
  129. $filepath = drupal_get_path('module', $module);
  130. if ($filepath && $filepath != '/') {
  131. $searchpath[] = $filepath;
  132. }
  133. }
  134. break;
  135. }
  136. return $searchpath;
  137. }
  138. /**
  139. * Check if the given command belongs to a disabled module.
  140. *
  141. * @return array
  142. * Array with a command-like bootstrap error or FALSE if Drupal was not
  143. * bootstrapped fully or the command does not belong to a disabled module.
  144. */
  145. function drush_command_belongs_to_disabled_module() {
  146. if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
  147. _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE, DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  148. drush_get_commands(TRUE);
  149. $commands = drush_get_commands();
  150. $arguments = drush_get_arguments();
  151. $command_name = array_shift($arguments);
  152. if (isset($commands[$command_name])) {
  153. // We found it. Load its module name and set an error.
  154. if (is_array($commands[$command_name]['drupal dependencies']) && count($commands[$command_name]['drupal dependencies'])) {
  155. $modules = implode(', ', $commands[$command_name]['drupal dependencies']);
  156. }
  157. else {
  158. // The command does not define Drupal dependencies. Derive them.
  159. $command_files = commandfiles_cache()->get();
  160. $command_path = $commands[$command_name]['path'] . DIRECTORY_SEPARATOR . $commands[$command_name]['commandfile'] . '.drush.inc';
  161. $modules = array_search($command_path, $command_files);
  162. }
  163. return array(
  164. 'bootstrap_errors' => array(
  165. 'DRUSH_COMMAND_DEPENDENCY_ERROR' => dt('Command !command needs the following module(s) enabled to run: !dependencies.', array(
  166. '!command' => $command_name,
  167. '!dependencies' => $modules,
  168. )),
  169. ),
  170. );
  171. }
  172. }
  173. return FALSE;
  174. }
  175. /**
  176. * Check that a command has its declared dependencies available or have no
  177. * dependencies.
  178. *
  179. * @param $command
  180. * Command to check. Any errors will be added to the 'bootstrap_errors' element.
  181. *
  182. * @return
  183. * TRUE if command is valid.
  184. */
  185. function drush_enforce_requirement_drupal_dependencies(&$command) {
  186. // If the command bootstrap is DRUSH_BOOTSTRAP_MAX, then we will
  187. // allow the requirements to pass if we have not successfully
  188. // bootstrapped Drupal. The combination of DRUSH_BOOTSTRAP_MAX
  189. // and 'drupal dependencies' indicates that the drush command
  190. // will use the dependent modules only if they are available.
  191. if ($command['bootstrap'] == DRUSH_BOOTSTRAP_MAX) {
  192. // If we have not bootstrapped, then let the dependencies pass;
  193. // if we have bootstrapped, then enforce them.
  194. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) {
  195. return TRUE;
  196. }
  197. }
  198. // If there are no drupal dependencies, then do nothing
  199. if (!empty($command['drupal dependencies'])) {
  200. foreach ($command['drupal dependencies'] as $dependency) {
  201. drush_include_engine('drupal', 'environment');
  202. if(!drush_module_exists($dependency)) {
  203. $command['bootstrap_errors']['DRUSH_COMMAND_DEPENDENCY_ERROR'] = dt('Command !command needs the following modules installed/enabled to run: !dependencies.', array('!command' => $command['command'], '!dependencies' => implode(', ', $command['drupal dependencies'])));
  204. return FALSE;
  205. }
  206. }
  207. }
  208. return TRUE;
  209. }
  210. /**
  211. * Validate the DRUSH_BOOTSTRAP_DRUPAL_ROOT phase.
  212. *
  213. * In this function, we will check if a valid Drupal directory is available.
  214. * We also determine the value that will be stored in the DRUSH_DRUPAL_ROOT
  215. * context and DRUPAL_ROOT constant if it is considered a valid option.
  216. */
  217. function bootstrap_drupal_root_validate() {
  218. $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
  219. if (empty($drupal_root)) {
  220. return drush_bootstrap_error('DRUSH_NO_DRUPAL_ROOT', dt("A Drupal installation directory could not be found"));
  221. }
  222. if (!$signature = drush_valid_root($drupal_root)) {
  223. return drush_bootstrap_error('DRUSH_INVALID_DRUPAL_ROOT', dt("The directory !drupal_root does not contain a valid Drupal installation", array('!drupal_root' => $drupal_root)));
  224. }
  225. $version = drush_drupal_version($drupal_root);
  226. $major_version = drush_drupal_major_version($drupal_root);
  227. if ($major_version <= 5) {
  228. return drush_set_error('DRUSH_DRUPAL_VERSION_UNSUPPORTED', dt('Drush !drush_version does not support Drupal !major_version.', array('!drush_version' => DRUSH_VERSION, '!major_version' => $major_version)));
  229. }
  230. drush_bootstrap_value('drupal_root', realpath($drupal_root));
  231. define('DRUSH_DRUPAL_SIGNATURE', $signature);
  232. return TRUE;
  233. }
  234. /**
  235. * Bootstrap Drush with a valid Drupal Directory.
  236. *
  237. * In this function, the pwd will be moved to the root
  238. * of the Drupal installation.
  239. *
  240. * The DRUSH_DRUPAL_ROOT context, DRUSH_DRUPAL_CORE context, DRUPAL_ROOT, and the
  241. * DRUSH_DRUPAL_CORE constants are populated from the value that we determined during
  242. * the validation phase.
  243. *
  244. * We also now load the drushrc.php for this specific Drupal site.
  245. * We can now include files from the Drupal Tree, and figure
  246. * out more context about the platform, such as the version of Drupal.
  247. */
  248. function bootstrap_drupal_root() {
  249. // Load the config options from Drupal's /drush and sites/all/drush directories.
  250. drush_load_config('drupal');
  251. $drupal_root = drush_set_context('DRUSH_DRUPAL_ROOT', drush_bootstrap_value('drupal_root'));
  252. chdir($drupal_root);
  253. $version = drush_drupal_version();
  254. $major_version = drush_drupal_major_version();
  255. $core = $this->bootstrap_drupal_core($drupal_root);
  256. // DRUSH_DRUPAL_CORE should point to the /core folder in Drupal 8+ or to DRUPAL_ROOT
  257. // in prior versions.
  258. drush_set_context('DRUSH_DRUPAL_CORE', $core);
  259. define('DRUSH_DRUPAL_CORE', $core);
  260. _drush_preflight_global_options();
  261. drush_log(dt("Initialized Drupal !version root directory at !drupal_root", array("!version" => $version, '!drupal_root' => $drupal_root)), LogLevel::BOOTSTRAP);
  262. }
  263. /**
  264. * VALIDATE the DRUSH_BOOTSTRAP_DRUPAL_SITE phase.
  265. *
  266. * In this function we determine the URL used for the command,
  267. * and check for a valid settings.php file.
  268. *
  269. * To do this, we need to set up the $_SERVER environment variable,
  270. * to allow us to use conf_path to determine what Drupal will load
  271. * as a configuration file.
  272. */
  273. function bootstrap_drupal_site_validate() {
  274. // Define the selected conf path as soon as we have identified that
  275. // we have selected a Drupal site. Drush used to set this context
  276. // during the drush_bootstrap_drush phase.
  277. $drush_uri = _drush_bootstrap_selected_uri();
  278. drush_set_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH', drush_conf_path($drush_uri));
  279. $this->bootstrap_drupal_site_setup_server_global($drush_uri);
  280. return $this->bootstrap_drupal_site_validate_settings_present();
  281. }
  282. /**
  283. * Set up the $_SERVER globals so that Drupal will see the same values
  284. * that it does when serving pages via the web server.
  285. */
  286. function bootstrap_drupal_site_setup_server_global($drush_uri) {
  287. // Fake the necessary HTTP headers that Drupal needs:
  288. if ($drush_uri) {
  289. $drupal_base_url = parse_url($drush_uri);
  290. // If there's no url scheme set, add http:// and re-parse the url
  291. // so the host and path values are set accurately.
  292. if (!array_key_exists('scheme', $drupal_base_url)) {
  293. $drush_uri = 'http://' . $drush_uri;
  294. $drupal_base_url = parse_url($drush_uri);
  295. }
  296. // Fill in defaults.
  297. $drupal_base_url += array(
  298. 'path' => NULL,
  299. 'host' => NULL,
  300. 'port' => NULL,
  301. );
  302. $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
  303. if ($drupal_base_url['scheme'] == 'https') {
  304. $_SERVER['HTTPS'] = 'on';
  305. }
  306. if ($drupal_base_url['port']) {
  307. $_SERVER['HTTP_HOST'] .= ':' . $drupal_base_url['port'];
  308. }
  309. $_SERVER['SERVER_PORT'] = $drupal_base_url['port'];
  310. if (array_key_exists('path', $drupal_base_url)) {
  311. $_SERVER['PHP_SELF'] = $drupal_base_url['path'] . '/index.php';
  312. }
  313. else {
  314. $_SERVER['PHP_SELF'] = '/index.php';
  315. }
  316. }
  317. else {
  318. $_SERVER['HTTP_HOST'] = 'default';
  319. $_SERVER['PHP_SELF'] = '/index.php';
  320. }
  321. $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
  322. $_SERVER['REQUEST_URI'] = '/';
  323. $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
  324. $_SERVER['REQUEST_METHOD'] = NULL;
  325. $_SERVER['SERVER_SOFTWARE'] = NULL;
  326. $_SERVER['HTTP_USER_AGENT'] = NULL;
  327. $_SERVER['SCRIPT_FILENAME'] = DRUPAL_ROOT . '/index.php';
  328. }
  329. /**
  330. * Validate that the Drupal site has all of the settings that it
  331. * needs to operated.
  332. */
  333. function bootstrap_drupal_site_validate_settings_present() {
  334. $site = drush_bootstrap_value('site', $_SERVER['HTTP_HOST']);
  335. $conf_path = drush_bootstrap_value('conf_path', \conf_path(TRUE, TRUE));
  336. $conf_file = "$conf_path/settings.php";
  337. if (!file_exists($conf_file)) {
  338. return drush_bootstrap_error('DRUPAL_SITE_SETTINGS_NOT_FOUND', dt("Could not find a Drupal settings.php file at !file.",
  339. array('!file' => $conf_file)));
  340. }
  341. return TRUE;
  342. }
  343. /**
  344. * Called by bootstrap_drupal_site to do the main work
  345. * of the drush drupal site bootstrap.
  346. */
  347. function bootstrap_do_drupal_site() {
  348. $drush_uri = drush_get_context('DRUSH_SELECTED_URI');
  349. drush_set_context('DRUSH_URI', $drush_uri);
  350. $site = drush_set_context('DRUSH_DRUPAL_SITE', drush_bootstrap_value('site'));
  351. $conf_path = drush_set_context('DRUSH_DRUPAL_SITE_ROOT', drush_bootstrap_value('conf_path'));
  352. drush_log(dt("Initialized Drupal site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path)), LogLevel::BOOTSTRAP);
  353. _drush_preflight_global_options();
  354. }
  355. /**
  356. * Initialize a site on the Drupal root.
  357. *
  358. * We now set various contexts that we determined and confirmed to be valid.
  359. * Additionally we load an optional drushrc.php file in the site directory.
  360. */
  361. function bootstrap_drupal_site() {
  362. drush_load_config('site');
  363. $this->bootstrap_do_drupal_site();
  364. }
  365. /**
  366. * Initialize and load the Drupal configuration files.
  367. *
  368. * We process and store a normalized set of database credentials
  369. * from the loaded configuration file, so we can validate them
  370. * and access them easily in the future.
  371. *
  372. * Also override Drupal variables as per --variables option.
  373. */
  374. function bootstrap_drupal_configuration() {
  375. global $conf;
  376. $override = array(
  377. 'dev_query' => FALSE, // Force Drupal6 not to store queries since we are not outputting them.
  378. 'cron_safe_threshold' => 0, // Don't run poormanscron during Drush request (D7+).
  379. );
  380. $current_override = drush_get_option_list('variables');
  381. foreach ($current_override as $name => $value) {
  382. if (is_numeric($name) && (strpos($value, '=') !== FALSE)) {
  383. list($name, $value) = explode('=', $value, 2);
  384. }
  385. $override[$name] = $value;
  386. }
  387. $conf = is_array($conf) ? array_merge($conf, $override) : $conf;
  388. }
  389. /**
  390. * Validate the DRUSH_BOOTSTRAP_DRUPAL_DATABASE phase
  391. *
  392. * Attempt to make a working database connection using the
  393. * database credentials that were loaded during the previous
  394. * phase.
  395. */
  396. function bootstrap_drupal_database_validate() {
  397. if (!drush_valid_db_credentials()) {
  398. return drush_bootstrap_error('DRUSH_DRUPAL_DB_ERROR');
  399. }
  400. return TRUE;
  401. }
  402. /**
  403. * Test to see if the Drupal database has a specified
  404. * table or tables.
  405. *
  406. * This is a bootstrap helper function designed to be called
  407. * from the bootstrap_drupal_database_validate() methods of
  408. * derived DrupalBoot classes. If a database exists, but is
  409. * empty, then the Drupal database bootstrap will fail. To
  410. * prevent this situation, we test for some table that is needed
  411. * in an ordinary bootstrap, and return FALSE from the validate
  412. * function if it does not exist, so that we do not attempt to
  413. * start the database bootstrap.
  414. *
  415. * Note that we must manually do our own prefix testing here,
  416. * because the existing wrappers we have for handling prefixes
  417. * depend on bootstrapping to the "database" phase, and therefore
  418. * are not available to validate this same phase.
  419. *
  420. * @param $required_tables
  421. * Array of table names, or string with one table name
  422. *
  423. * @return TRUE if all tables in input parameter exist in
  424. * the database.
  425. */
  426. function bootstrap_drupal_database_has_table($required_tables) {
  427. try {
  428. $sql = drush_sql_get_class();
  429. $spec = $sql->db_spec();
  430. $prefix = isset($spec['prefix']) ? $spec['prefix'] : NULL;
  431. if (!is_array($prefix)) {
  432. $prefix = array('default' => $prefix);
  433. }
  434. $tables = $sql->listTables();
  435. foreach ((array)$required_tables as $required_table) {
  436. $prefix_key = array_key_exists($required_table, $prefix) ? $required_table : 'default';
  437. if (!in_array($prefix[$prefix_key] . $required_table, $tables)) {
  438. return FALSE;
  439. }
  440. }
  441. }
  442. catch (Exception $e) {
  443. // Usually the checks above should return a result without
  444. // throwing an exception, but we'll catch any that are
  445. // thrown just in case.
  446. return FALSE;
  447. }
  448. return TRUE;
  449. }
  450. /**
  451. * Boostrap the Drupal database.
  452. */
  453. function bootstrap_drupal_database() {
  454. // We presume that our derived classes will connect and then
  455. // either fail, or call us via parent::
  456. drush_log(dt("Successfully connected to the Drupal database."), LogLevel::BOOTSTRAP);
  457. }
  458. /**
  459. * Attempt to load the full Drupal system.
  460. */
  461. function bootstrap_drupal_full() {
  462. drush_include_engine('drupal', 'environment');
  463. $this->add_logger();
  464. // Write correct install_profile to cache as needed. Used by _drush_find_commandfiles().
  465. $cid = drush_cid_install_profile();
  466. $install_profile = $this->get_profile();
  467. if ($cached_install_profile = drush_cache_get($cid)) {
  468. // We have a cached profile. Check it for correctness and save new value if needed.
  469. if ($cached_install_profile->data != $install_profile) {
  470. drush_cache_set($cid, $install_profile);
  471. }
  472. }
  473. else {
  474. // No cached entry so write to cache.
  475. drush_cache_set($cid, $install_profile);
  476. }
  477. _drush_log_drupal_messages();
  478. }
  479. /**
  480. * Log into the bootstrapped Drupal site with a specific
  481. * username or user id.
  482. */
  483. function bootstrap_drupal_login() {
  484. $uid_or_name = drush_set_context('DRUSH_USER', drush_get_option('user', 0));
  485. $userversion = drush_user_get_class();
  486. if (!$account = $userversion->load_by_uid($uid_or_name)) {
  487. if (!$account = $userversion->load_by_name($uid_or_name)) {
  488. if (is_numeric($uid_or_name)) {
  489. $message = dt('Could not login with user ID !user.', array('!user' => $uid_or_name));
  490. if ($uid_or_name === 0) {
  491. $message .= ' ' . dt('This is typically caused by importing a MySQL database dump from a faulty tool which re-numbered the anonymous user ID in the users table. See !link for help recovering from this situation.', array('!link' => 'http://drupal.org/node/1029506'));
  492. }
  493. }
  494. else {
  495. $message = dt('Could not login with user account `!user\'.', array('!user' => $uid_or_name));
  496. }
  497. return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message);
  498. }
  499. }
  500. $userversion->setCurrentUser($account);
  501. _drush_log_drupal_messages();
  502. }
  503. }