5 * Functions used by drush to query the environment and
6 * setting the current configuration.
8 * Bootstrapping now occurs in bootstrap.inc.
10 * @see includes/bootstrap.inc
13 use Drush\Log\LogLevel;
14 use Webmozart\PathUtil\Path;
17 * Log PHP errors to the Drush log. This is in effect until Drupal's error
20 function drush_error_handler($errno, $message, $filename, $line, $context) {
21 // E_DEPRECATED was added in PHP 5.3. Drupal 6 will not fix all the
22 // deprecated errors, but suppresses them. So we suppress them as well.
23 if (defined('E_DEPRECATED')) {
24 $errno = $errno & ~E_DEPRECATED;
27 // "error_reporting" is usually set in php.ini, but may be changed by
28 // drush_errors_on() and drush_errors_off().
29 if ($errno & error_reporting()) {
30 // By default we log notices.
31 $type = drush_get_option('php-notices', 'notice');
32 $halt_on_error = drush_get_option('halt-on-error', (drush_drupal_major_version() != 6));
34 // Bitmask value that constitutes an error needing to be logged.
35 $error = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
36 if ($errno & $error) {
40 // Bitmask value that constitutes a warning being logged.
41 $warning = E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING;
42 if ($errno & $warning) {
43 $type = LogLevel::WARNING;
46 drush_log($message . ' ' . basename($filename) . ':' . $line, $type);
48 if ($errno == E_RECOVERABLE_ERROR && $halt_on_error) {
49 drush_log(dt('E_RECOVERABLE_ERROR encountered; aborting. To ignore recoverable errors, run again with --no-halt-on-error'), 'error');
50 exit(DRUSH_APPLICATION_ERROR);
58 * Returns a localizable message about php.ini that
59 * varies depending on whether the php_ini_loaded_file()
60 * is available or not.
62 function _drush_php_ini_loaded_file_message() {
63 if (function_exists('php_ini_loaded_file')) {
64 return dt('Please check your configuration settings in !phpini or in your drush.ini file; see examples/example.drush.ini for details.', array('!phpini' => php_ini_loaded_file()));
67 return dt('Please check your configuration settings in your php.ini file or in your drush.ini file; see examples/example.drush.ini for details.');
72 * Evalute the environment after an abnormal termination and
73 * see if we can determine any configuration settings that the user might
76 function _drush_postmortem() {
77 // Make sure that the memory limit has been bumped up from the minimum default value of 32M.
78 $php_memory_limit = drush_memory_limit();
79 if (($php_memory_limit > 0) && ($php_memory_limit <= 32*DRUSH_KILOBYTE*DRUSH_KILOBYTE)) {
80 drush_set_error('DRUSH_MEMORY_LIMIT', dt('Your memory limit is set to !memory_limit; Drush needs as much memory to run as Drupal. !php_ini_msg', array('!memory_limit' => $php_memory_limit / (DRUSH_KILOBYTE*DRUSH_KILOBYTE) . 'M', '!php_ini_msg' => _drush_php_ini_loaded_file_message())));
85 * Evaluate the environment before command bootstrapping
86 * begins. If the php environment is too restrictive, then
87 * notify the user that a setting change is needed and abort.
89 function _drush_environment_check_php_ini() {
90 $ini_checks = array('safe_mode' => '', 'open_basedir' => '', 'disable_functions' => array('exec', 'system'), 'disable_classes' => '');
92 // Test to insure that certain php ini restrictions have not been enabled
93 $prohibited_list = array();
94 foreach ($ini_checks as $prohibited_mode => $disallowed_value) {
95 $ini_value = ini_get($prohibited_mode);
96 $invalid_value = FALSE;
97 if (empty($disallowed_value)) {
98 $invalid_value = !empty($ini_value) && (strcasecmp($ini_value, 'off') != 0);
101 foreach ($disallowed_value as $test_value) {
102 if (preg_match('/(^|,)' . $test_value . '(,|$)/', $ini_value)) {
103 $invalid_value = TRUE;
107 if ($invalid_value) {
108 $prohibited_list[] = $prohibited_mode;
111 if (!empty($prohibited_list)) {
112 drush_log(dt('The following restricted PHP modes have non-empty values: !prohibited_list. This configuration is incompatible with drush. !php_ini_msg', array('!prohibited_list' => implode(' and ', $prohibited_list), '!php_ini_msg' => _drush_php_ini_loaded_file_message())), LogLevel::ERROR);
119 * Returns the current working directory.
121 * This is the directory as it was when drush was started, not the
122 * directory we are currently in. For that, use getcwd() directly.
124 function drush_cwd() {
125 if ($path = drush_get_context('DRUSH_OLDCWD')) {
128 // We use PWD if available because getcwd() resolves symlinks, which
129 // could take us outside of the Drupal root, making it impossible to find.
130 // $_SERVER['PWD'] isn't set on windows and generates a Notice.
131 $path = isset($_SERVER['PWD']) ? $_SERVER['PWD'] : '';
136 // Convert windows paths.
137 $path = Path::canonicalize($path);
139 // Save original working dir case some command wants it.
140 drush_set_context('DRUSH_OLDCWD', $path);
146 * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3).
147 * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a
148 * proper drive path, still with Unix slashes (c:/dir1).
150 function _drush_convert_path($path) {
151 $path = str_replace('\\','/', $path);
152 if (drush_is_windows(_drush_get_os()) && !drush_is_cygwin(_drush_get_os())) {
153 $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path);
160 * Returns parent directory.
163 * Path to start from.
166 * Parent path of given path.
168 function _drush_shift_path_up($path) {
172 $path = explode(DIRECTORY_SEPARATOR, $path);
173 // Move one directory up.
175 return implode(DIRECTORY_SEPARATOR, $path);
176 // return dirname($path);
180 * Like Drupal conf_path, but searching from beneath.
181 * Allows proper site uri detection in site sub-directories.
183 * Essentially looks for a settings.php file. Drush uses this
184 * function to find a usable site based on the user's current
188 * Search starting path. Defaults to current working directory.
191 * Current site path (folder containing settings.php) or FALSE if not found.
193 function drush_site_path($path = NULL) {
196 $path = empty($path) ? drush_cwd() : $path;
197 // Check the current path.
198 if (file_exists($path . '/settings.php')) {
202 // Move up dir by dir and check each.
203 // Stop if we get to a Drupal root. We don't care
204 // if it is DRUSH_SELECTED_DRUPAL_ROOT or some other root.
205 while (($path = _drush_shift_path_up($path)) && !drush_valid_root($path)) {
206 if (file_exists($path . '/settings.php')) {
213 $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
214 if (file_exists($site_root . '/sites/sites.php')) {
216 // This will overwrite $sites with the desired mappings.
217 include($site_root . '/sites/sites.php');
218 // We do a reverse lookup here to determine the URL given the site key.
219 if ($match = array_search($site_path, $sites)) {
224 // Last resort: try from site root
227 if (file_exists($site_root . '/sites/default/settings.php')) {
228 $site_path = $site_root . '/sites/default';
237 * Lookup a site's directory via the sites.php file given a hostname.
240 * The hostname of a site. May be converted from URI.
243 * The directory associated with that hostname or FALSE if not found.
245 function drush_site_dir_lookup_from_hostname($hostname, $site_root = NULL) {
246 if (!isset($site_root)) {
247 $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
249 if (!empty($site_root) && file_exists($site_root . '/sites/sites.php')) {
251 // This will overwrite $sites with the desired mappings.
252 include($site_root . '/sites/sites.php');
253 return isset($sites[$hostname]) ? $sites[$hostname] : FALSE;
261 * This is a copy of Drupal's conf_path function, taken from D7 and
262 * adjusted slightly to search from the selected Drupal Root.
264 * Drush uses this routine to find a usable site based on a URI
265 * passed in via a site alias record or the --uri commandline option.
267 * Drush uses Drupal itself (specifically, the Drupal conf_path function)
268 * to bootstrap the site itself. If the implementation of conf_path
269 * changes, the site should still bootstrap correctly; the only consequence
270 * of this routine not working is that drush configuration files
271 * (drushrc.php) stored with the site's drush folders might not be found.
273 function drush_conf_path($server_uri, $require_settings = TRUE) {
274 $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
275 if(empty($drupal_root) || empty($server_uri)) {
278 $parsed_uri = parse_url($server_uri);
279 if (is_array($parsed_uri) && !array_key_exists('scheme', $parsed_uri)) {
280 $parsed_uri = parse_url('http://' . $server_uri);
282 if (!is_array($parsed_uri)) {
285 $server_host = $parsed_uri['host'];
286 if (array_key_exists('path', $parsed_uri)) {
287 $server_uri = $parsed_uri['path'] . '/index.php';
290 $server_uri = "/index.php";
295 if (file_exists($drupal_root . '/' . $confdir . '/sites.php')) {
296 // This will overwrite $sites with the desired mappings.
297 include($drupal_root . '/' . $confdir . '/sites.php');
300 $uri = explode('/', $server_uri);
301 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($server_host, '.')))));
302 for ($i = count($uri) - 1; $i > 0; $i--) {
303 for ($j = count($server); $j > 0; $j--) {
304 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
305 if (isset($sites[$dir]) && file_exists($drupal_root . '/' . $confdir . '/' . $sites[$dir])) {
308 if (file_exists($drupal_root . '/' . $confdir . '/' . $dir . '/settings.php') || (!$require_settings && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir))) {
309 $conf = "$confdir/$dir";
314 $conf = "$confdir/default";
319 * Exhaustive depth-first search to try and locate the Drupal root directory.
320 * This makes it possible to run Drush from a subdirectory of the drupal root.
323 * Search start path. Defaults to current working directory.
325 * A path to drupal root, or FALSE if not found.
327 function drush_locate_root($start_path = NULL) {
328 $drupal_root = FALSE;
330 $start_path = empty($start_path) ? drush_cwd() : $start_path;
331 foreach (array(TRUE, FALSE) as $follow_symlinks) {
333 if ($follow_symlinks && is_link($path)) {
334 $path = realpath($path);
336 // Check the start path.
337 if (drush_valid_root($path)) {
338 $drupal_root = $path;
342 // Move up dir by dir and check each.
343 while ($path = _drush_shift_path_up($path)) {
344 if ($follow_symlinks && is_link($path)) {
345 $path = realpath($path);
347 if (drush_valid_root($path)) {
348 $drupal_root = $path;
359 * Checks whether given path qualifies as a Drupal root.
365 * The relative path to common.inc (varies by Drupal version), or FALSE if not
368 function drush_valid_root($path) {
369 $bootstrap_class = drush_bootstrap_class_for_root($path);
370 return $bootstrap_class != NULL;
374 * Tests the currently loaded database credentials to ensure a database connection can be made.
376 * @param bool $log_errors
377 * (optional) If TRUE, log error conditions; otherwise be quiet.
380 * TRUE if database credentials are valid.
382 function drush_valid_db_credentials() {
384 $sql = drush_sql_get_class();
385 if (!$sqlVersion = drush_sql_get_version()) {
386 drush_log(dt('While checking DB credentials, could not instantiate SQLVersion class.'), 'debug');
389 if (!$sqlVersion->valid_credentials($sql->db_spec())) {
390 drush_log(dt('DB credentials are invalid.'), 'debug');
393 return $sql->query('SELECT 1;');
395 catch (Exception $e) {
396 drush_log(dt('Checking DB credentials yielded error: @e', array('@e' => $e->getMessage())), 'debug');
402 * Determine a proper way to call drush again
404 * This check if we were called directly or as an argument to some
405 * wrapper command (php and sudo are checked now).
407 * Calling ./drush.php directly yields the following environment:
409 * _SERVER["argv"][0] => ./drush.php
411 * Calling php ./drush.php also yields the following:
413 * _SERVER["argv"][0] => ./drush.php
415 * Note that the $_ global is defined only in bash and therefore cannot
418 * The DRUSH_COMMAND constant is initialised to the value of this
419 * function when environment.inc is loaded.
423 function drush_find_drush() {
424 if ($drush = realpath($_SERVER['argv']['0'])) {
425 return Path::canonicalize($drush);
431 * Verify that we are running PHP through the command line interface.
433 * This function is useful for making sure that code cannot be run via the web server,
434 * such as a function that needs to write files to which the web server should not have
438 * A boolean value that is true when PHP is being run through the command line,
439 * and false if being run through cgi or mod_php.
441 function drush_verify_cli() {
442 return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0));
446 * Build a drush command suitable for use for Drush to call itself
447 * e.g. in backend_invoke.
449 function drush_build_drush_command($drush_path = NULL, $php = NULL, $os = NULL, $remote_command = FALSE, $environment_variables = array()) {
450 $os = _drush_get_os($os);
451 $additional_options = '';
454 if (!$remote_command) {
455 $drush_path = DRUSH_COMMAND;
458 $drush_path = 'drush'; // drush_is_windows($os) ? 'drush.bat' : 'drush';
461 // If the path to drush points to drush.php, then we will need to
462 // run it via php rather than direct execution. By default, we
463 // will use 'php' unless something more specific was passed in
464 // via the --php flag.
465 if (substr($drush_path, -4) == ".php") {
467 $php = drush_get_option('php');
472 if (isset($php) && ($php != "php")) {
473 $additional_options .= ' --php=' . drush_escapeshellarg($php, $os);
475 // We will also add in the php options from --php-options
476 $prefix .= drush_escapeshellarg($php, $os);
477 $php_options = implode(' ', drush_get_context_options('php-options'));
478 if (!empty($php_options)) {
479 $prefix .= ' ' . $php_options;
480 $additional_options .= ' --php-options=' . drush_escapeshellarg($php_options, $os);
484 // Set environment variables to propogate config to redispatched calls.
485 if (drush_has_bash($os)) {
487 $environment_variables['DRUSH_PHP'] = $php;
489 if ($php_options_alias = drush_get_option('php-options', NULL, 'alias')) {
490 $environment_variables['PHP_OPTIONS'] = $php_options_alias;
492 $columns = drush_get_context('DRUSH_COLUMNS');
493 if (($columns) && ($columns != 80)) {
494 $environment_variables['COLUMNS'] = $columns;
499 // Add environmental variables, if present
500 if (!empty($environment_variables)) {
502 foreach ($environment_variables as $key=>$value) {
503 $env .= ' ' . drush_escapeshellarg($key, $os) . '=' . drush_escapeshellarg($value, $os);
505 $prefix = $env . ' ' . $prefix;
508 return trim($prefix . ' ' . drush_escapeshellarg($drush_path, $os) . $additional_options);
512 * Check if the operating system is Winodws
513 * running some variant of cygwin -- either
514 * Cygwin or the MSYSGIT shell. If you care
515 * which is which, test mingw first.
517 function drush_is_cygwin($os = NULL) {
518 return _drush_test_os($os, array("CYGWIN","CWRSYNC","MINGW"));
521 function drush_is_mingw($os = NULL) {
522 return _drush_test_os($os, array("MINGW"));
526 * Return tar executable name specific for the current OS
528 function drush_get_tar_executable() {
529 return drush_is_windows() ? (drush_is_mingw() ? "tar.exe" : "bsdtar.exe") : "tar";
533 * Check if the operating system is OS X.
534 * This will return TRUE for Mac OS X (Darwin).
536 function drush_is_osx($os = NULL) {
537 return _drush_test_os($os, array("DARWIN"));
541 * Checks if the operating system has bash.
543 * MinGW has bash, but PHP isn't part of MinGW and hence doesn't run in bash.
545 function drush_has_bash($os = NULL) {
546 return (drush_is_cygwin($os) && !drush_is_mingw($os)) || !drush_is_windows($os);
550 * Checks operating system and returns
551 * supported bit bucket folder.
553 function drush_bit_bucket() {
554 if (drush_has_bash()) {
563 * Return the OS we are running under.
569 * MINGW* (e.g. MINGW32)
571 function _drush_get_os($os = NULL) {
572 // The special os "CWRSYNC" can be used to indicate that we are testing
573 // a path that will be passed as an argument to cwRsync, which requires
574 // that the path be converted to /cygdrive/c/path, even on DOS or Powershell.
575 // The special os "RSYNC" can be used to indicate that we want to assume
576 // "CWRSYNC" when cwrsync is installed, or default to the local OS otherwise.
577 if (strtoupper($os) == "RSYNC") {
578 $os = _drush_get_os("LOCAL");
579 // For now we assume that cwrsync is always installed on Windows, and never installed son any other platform.
580 return drush_is_windows($os) ? "CWRSYNC" : $os;
582 // We allow "LOCAL" to document, in instances where some parameters are being escaped
583 // for use on a remote machine, that one particular parameter will always be used on
584 // the local machine (c.f. drush_backend_invoke).
585 if (isset($os) && ($os != "LOCAL")) {
588 if (_drush_test_os(getenv("MSYSTEM"), array("MINGW"))) {
589 return getenv("MSYSTEM");
591 // QUESTION: Can we differentiate between DOS and POWERSHELL? They appear to have the same environment.
592 // At the moment, it does not seem to matter; they behave the same from PHP.
593 // At this point we will just return PHP_OS.
597 function _drush_test_os($os, $os_list_to_check) {
598 $os = _drush_get_os($os);
599 foreach ($os_list_to_check as $test) {
600 if (strtoupper(substr($os, 0, strlen($test))) == strtoupper($test)) {
608 * Read the drush info file.
610 function drush_read_drush_info() {
611 $drush_info_file = dirname(__FILE__) . '/../drush.info';
613 return parse_ini_file($drush_info_file);
617 * Make a determination whether or not the given
618 * host is local or not.
621 * A hostname, 'localhost' or '127.0.0.1'.
623 * True if the host is local.
625 function drush_is_local_host($host) {
626 // Check to see if the provided host is "local".
627 // @see hook_drush_sitealias_alter() in drush.api.php.
629 ($host == 'localhost') ||
630 ($host == '127.0.0.1')
639 * Return the user's home directory.
641 function drush_server_home() {
643 return Path::getHomeDirectory();
644 } catch (Exception $e) {
650 * Return the name of the user running drush.
652 function drush_get_username() {
654 if (!$name = getenv("username")) { // Windows
655 if (!$name = getenv("USER")) {
656 // If USER not defined, use posix
657 if (function_exists('posix_getpwuid')) {
658 $processUser = posix_getpwuid(posix_geteuid());
659 $name = $processUser['name'];
667 * The path to the global cache directory.
670 * Return the specified subdirectory inside the global
671 * cache directory instead. The subdirectory is created.
673 function drush_directory_cache($subdir = '') {
674 $cache_locations = array();
675 if (getenv('CACHE_PREFIX')) {
676 $cache_locations[getenv('CACHE_PREFIX')] = 'cache';
678 $home = drush_server_home();
680 $cache_locations[$home] = '.drush/cache';
682 $cache_locations[drush_find_tmp()] = 'drush-' . drush_get_username() . '/cache';
683 foreach ($cache_locations as $base => $dir) {
684 if (!empty($base) && is_writable($base)) {
685 $cache_dir = $base . '/' . $dir;
686 if (!empty($subdir)) {
687 $cache_dir .= '/' . $subdir;
689 if (drush_mkdir($cache_dir)) {
693 // If the base directory is writable, but the cache directory
694 // is not, then we will get an error. The error will be displayed,
695 // but we will still call drush_clear_error so that we can go
696 // on and try the next location to see if we can find a cache
697 // directory somewhere.
702 return drush_set_error('DRUSH_NO_WRITABLE_CACHE', dt('Drush must have a writable cache directory; please insure that one of the following locations is writable: @locations',
703 array('@locations' => implode(',', array_keys($cache_locations)))));
707 * Get complete information for all available extensions (modules and themes).
710 * An array containing info for all available extensions. In D8, these are Extension objects.
712 function drush_get_extensions($include_hidden = TRUE) {
713 drush_include_engine('drupal', 'environment');
714 $extensions = array_merge(drush_get_modules($include_hidden), drush_get_themes($include_hidden));
715 foreach ($extensions as $name => $extension) {
716 if (isset($extension->info['name'])) {
717 $extensions[$name]->label = $extension->info['name'].' ('.$name.')';
720 drush_log(dt("Extension !name provides no human-readable name in .info file.", array('!name' => $name), LogLevel::DEBUG));
721 $extensions[$name]->label = $name.' ('.$name.')';
723 if (empty($extension->info['package'])) {
724 $extensions[$name]->info['package'] = dt('Other');
731 * Gets the extension name.
734 * The extension info.
736 * The extension name.
738 function drush_extension_get_name($info) {
739 drush_include_engine('drupal', 'environment');
740 return _drush_extension_get_name($info);
744 * Gets the extension type.
747 * The extension info.
749 * The extension type.
751 function drush_extension_get_type($info) {
752 drush_include_engine('drupal', 'environment');
753 return _drush_extension_get_type($info);
757 * Gets the extension path.
760 * The extension info.
762 * The extension path.
764 function drush_extension_get_path($info) {
765 drush_include_engine('drupal', 'environment');
766 return _drush_extension_get_path($info);
770 * Test compatibility of a extension with version of drupal core and php.
772 * @param $file Extension object as returned by system_rebuild_module_data().
773 * @return FALSE if the extension is compatible.
775 function drush_extension_check_incompatibility($file) {
776 if (!isset($file->info['core']) || $file->info['core'] != drush_get_drupal_core_compatibility()) {
779 if (version_compare(phpversion(), $file->info['php']) < 0) {
788 function drush_drupal_required_modules($modules) {
789 drush_include_engine('drupal', 'environment');
790 return _drush_drupal_required_modules($modules);
794 * Return the default theme.
797 * Machine name of the default theme.
799 function drush_theme_get_default() {
800 drush_include_engine('drupal', 'environment');
801 return _drush_theme_default();
805 * Return the administration theme.
808 * Machine name of the administration theme.
810 function drush_theme_get_admin() {
811 drush_include_engine('drupal', 'environment');
812 return _drush_theme_admin();
816 * Return the path to public files directory.
818 function drush_file_get_public() {
819 drush_include_engine('drupal', 'environment');
820 return _drush_file_public_path();
824 * Return the path to private files directory.
826 function drush_file_get_private() {
827 drush_include_engine('drupal', 'environment');
828 return _drush_file_private_path();
832 * Returns the sitewide Drupal directory for extensions.
834 function drush_drupal_sitewide_directory($major_version = NULL) {
835 if (!$major_version) {
836 $major_version = drush_drupal_major_version();
838 return ($major_version < 8) ? 'sites/all' : '';
842 * Helper function to get core compatibility constant.
845 * The Drupal core compatibility constant.
847 function drush_get_drupal_core_compatibility() {
848 if (defined('DRUPAL_CORE_COMPATIBILITY')) {
849 return DRUPAL_CORE_COMPATIBILITY;
851 elseif (defined('\Drupal::CORE_COMPATIBILITY')) {
852 return \Drupal::CORE_COMPATIBILITY;
857 * Set Env. Variables for given site-alias.
859 function drush_set_environment_vars(array $site_record) {
860 if (!empty($site_record)) {
861 $os = drush_os($site_record);
862 if (isset($site_record['#env-vars'])) {
863 foreach ($site_record['#env-vars'] as $var => $value) {
864 $env_var = drush_escapeshellarg($var, $os, TRUE) . '=' . drush_escapeshellarg($value, $os, TRUE);