pax_global_header00006660000000000000000000000064122210554610014510gustar00rootroot0000000000000052 comment=d5aa66cdd1cd2028e4a131779b588aa163af33f1 drush-5.10.0/000077500000000000000000000000001222105546100127205ustar00rootroot00000000000000drush-5.10.0/.gitignore000066400000000000000000000000301222105546100147010ustar00rootroot00000000000000lib/* tests/phpunit.xml drush-5.10.0/.travis.yml000066400000000000000000000034431222105546100150350ustar00rootroot00000000000000# Configuration file for unit test runner at http://travis-ci.org/#!/drush-ops/drush # whitelist branches: only: - 6.x - 5.x language: php php: - 5.3 # - 5.4 env: - UNISH_DB_URL=mysql://root:@127.0.0.1 PHPUNIT_ARGS=--group=make - UNISH_DB_URL=mysql://root:@127.0.0.1 PHPUNIT_ARGS=--group=base - UNISH_DB_URL=mysql://root:@127.0.0.1 PHPUNIT_ARGS=--group=commands - UNISH_DB_URL=mysql://root:@127.0.0.1 PHPUNIT_ARGS=--group=pm - UNISH_DB_URL=mysql://root:@127.0.0.1 PHPUNIT_ARGS=--group=quick-drupal - UNISH_DB_URL=mysql://root:@127.0.0.1 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal # - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=make # - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=base # - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=commands # - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=pm # - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=quick-drupal # - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=make - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=base - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=commands - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=pm - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=quick-drupal - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal notifications: irc: "irc.freenode.org#drush" before_script: - echo "sendmail_path='true'" >> `php --ini | grep "Loaded Configuration" | awk '{print $4}'`; export UNISH_DRUSH="${PWD}/drush" && cd tests script: phpunit $PHPUNIT_ARGS drush-5.10.0/CONTRIBUTING.md000066400000000000000000000051101222105546100151460ustar00rootroot00000000000000Drush is built by people like you! Please [join us](https://github.com/drush-ops/drush). ## Git and Pull requests * Contributions are submitted, reviewed, and accepted using Github pull requests. [Read this article](https://help.github.com/articles/using-pull-requests) for some details. We use the _Fork and Pull_ model, as described there. * Optionally, to help keep track of [your assigned issues](https://github.com/dashboard/issues/assigned), simply ask to be added to the contributor team. A maintainer can now assign any issue to you at your request. * The latest changes are in the `master` branch. * Make a new branch for every feature you're working on. * Try to make clean commits that are easy readable (including descriptive commit messages!) * Test before you push. Get familiar with Unish, our test suite. See the test-specific [README.md](tests/README.md) * Make small pull requests that are easy to review but make sure they do add value by themselves. * We maintain branches named 6.x, 5.x, etc. These are release branches. From these branches, we make new tags for patch and minor versions. ## Coding style * Do write comments. You don't have to comment every line, but if you come up with something thats a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are nearly worthless! * We use [Drupal's coding standards](https://drupal.org/coding-standards). * Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time! * Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!) * Keep it compatible. Do not introduce changes to the public API, or configurations too lightly. Don't make incompatible changes without good reasons! ## Documentation * The docs are in the [docs](docs) and [examples](examples) folders in the git repository, so people can easily find the suitable docs for the current git revision. You can read these from within Drush, with the `drush topic` command. * Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request. * You can build the docs e.g. produce html, using `drush help --html`. This page also resides at http://www.drush.org. You might enjoy prettier and more comprehensive version at [Drush Commands](http://www.drushcommands.com) drush-5.10.0/README.md000066400000000000000000000430641222105546100142060ustar00rootroot00000000000000DESCRIPTION ----------- Drush is a command line shell and Unix scripting interface for Drupal. If you are unfamiliar with shell scripting, reviewing the documentation for your shell (e.g. man bash) or reading an online tutorial (e.g. search for "bash tutorial") will help you get the most out of Drush. Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. DRUSH VERSIONS -------------- Each version of Drush supports multiple Drupal versions. Drush 6 is recommended version. Drush Version | Branch | PHP | Compatible Drupal versions | Code Status ------------- | ------ | --- | -------------------------- | ----------- Drush 7 | [master](https://travis-ci.org/drush-ops/drush) | 5.3.3+ | D6, D7, D8 | Drush 6 | [6.x](https://travis-ci.org/drush-ops/drush) | 5.3.3+ | D6, D7 | Drush 5 | [5.x](https://travis-ci.org/drush-ops/drush) | 5.2.0+ | D6, D7 | Drush 4 | 4.x | 5.2.0+ | D5, D6, D7 | Unsupported Drush 3 | 3.x | 5.2.0+ | D5, D6 | Unsupported Drush comes with a full test suite powered by [PHPUnit](https://github.com/sebastianbergmann/phpunit). Each commit gets tested by the awesome [Travis.ci continuous integration service](https://travis-ci.org/drush-ops/drush). USAGE ----------- Drush can be run in your shell by typing "drush" from within any Drupal root directory. $ drush [options] [argument1] [argument2] Use the 'help' command to get a list of available options and commands: $ drush help For even more documentation, use the 'topic' command: $ drush topic Installation instructions can be found below. For a full list of Drush commands and documentation by version, visit http://www.drush.org. SUPPORT ----------- Please take a moment to review the rest of the information in this file before pursuing one of the support options below. * Post support requests to [Drupal Answers](http://drupal.stackexchange.com/questions/tagged/drush). * Bug reports and feature requests should be reported in the [GitHub Drush Issue Queue](https://github.com/drush-ops/drush/issues). * Use pull requests (PRs) to contribute to Drush. See [/CONTRIBUTING.md](CONTRIBUTING.md). * It is still possible to search the old issue queue on Drupal.org for [fixed bugs](https://drupal.org/project/issues/search/drush?status%5B%5D=7&categories%5B%5D=bug), [unmigrated issues](https://drupal.org/project/issues/search/drush?status%5B%5D=5&issue_tags=needs+migration), [unmigrated bugs](https://drupal.org/project/issues/search/drush?status%5B%5D=5&categories%5B%5D=bug&issue_tags=needs+migration), and so on. MISC ----------- * [www.drush.org](http://www.drush.org) * [A list of modules that include Drush integration](http://drupal.org/project/modules?filters=tid%3A4654) * For more information, please see the [Resources](http://drush.org/resources) and the [Drush FAQ](http://drupal.org/drush-faq). Run the `drush topic` command for even more help. * If you are using Debian or Ubuntu, you can alternatively use the Debian packages uploaded in your distribution. You may need to use the backports to get the latest version, if you are running a LTS or "stable" release. * For advice on using Drush with your ISP, see the hosting section of the Resources page on drush.org. REQUIREMENTS ----------- * To use Drush, you'll need a command line PHP version 5.3.3+. * Drush commands that work with git require git 1.7 or greater. * Drush works best on a Unix-like OS (Linux, OS X) * Most Drush commands run on Windows. See INSTALLING DRUSH ON WINDOWS, below. INSTALL - PEAR ----------- If you have trouble with PEAR installation, consider trying MANUAL INSTALLATION. It is not too hard. ```bash pear channel-discover pear.drush.org pear install drush/drush ``` _Tip: Use sudo to overcome permission problems. If the channel-discover fails, try running the following sequence of commands:_ ```bash pear upgrade --force Console_Getopt pear upgrade --force pear pear upgrade-all ``` To update, run `pear upgrade drush/drush` To get alternate drush versions, replace that last line with one of the below that matches your fancy. ```bash pear install drush/drush-5.0.0 pear install drush/drush-6.0.0RC4 ``` See the POST-INSTALL section for configuration tips. INSTALL - MANUAL ----------- 1. Place the uncompressed drush.tar.gz, drush.zip, or cloned git repository in a directory that is outside of your web root. 1. Make the 'drush' command executable: `$ chmod u+x /path/to/drush/drush` 1. Configure your system to recognize where Drush resides. There are 2 options: 1. Create a symbolic link to the Drush executable in a directory that is already in your PATH, e.g.: `$ ln -s /path/to/drush/drush /usr/bin/drush` 1. Explicitly add the Drush executable to the PATH variable which is defined in the the shell configuration file called .profile, .bash_profile, .bash_aliases, or .bashrc that is located in your home folder, i.e.: `export PATH="$PATH:/path/to/drush:/usr/local/bin"` Your system will search path options from left to right until it finds a result. To apply your changes to your current session, either log out and then log back in again, or re-load your bash configuration file, i.e.: `$ source .bashrc` NOTE: If you do not follow step 3, you will need to inconveniently run Drush commands using the full path to the executable "/path/to/drush/drush" or by navigating to /path/to/drush and running "./drush". The -r or -l options will be required (see USAGE, below). 1. Test that Drush is found by your system: `$ which drush` See the POST-INSTALL section for configuration tips. POST-INSTALL ----------------------- 1. See [example.bashrc](examples/example.bashrc) for instructions on how to add some useful shell aliases that provides even tighter integration between drush and bash. You may source this file directly into your shell by adding to your .bashrc (or equivalent): source /path/to/drush/examples/example.bashrc 1. If you didn't source it the step above, see top of [drush.complete.sh](drush.complete.sh) file for instructions adding bash completion for drush command to your shell. Once configured, completion works for site aliases, command names, shell aliases, global options, and command-specific options. 1. Optional. If [drush.complete.sh](drush.complete.sh) is being sourced (ideally in bash_completion.d), you can use the supplied __drush_ps1() sh function to add your current drush site (set with `drush use @sitename`) to your PS1 prompt like so: ```bash if [ "\$(type -t __git_ps1)" ] && [ "\$(type -t __drush_ps1)" ]; then PS1='\u@\h \w$(__git_ps1 " (%s)")$(__drush_ps1 "[%s]")\$ ' fi ``` Putting this in a .bashrc/.bash_profile/.profile would produce this prompt: `msonnabaum@hostname ~/repos/drush (master)[@sitename]$` 1. Help the Drush development team by sending anonymized usage statistics. To automatically send usage data, please add the following to a .drushrc.php file: ```php $options['drush_usage_log'] = TRUE; $options['drush_usage_send'] = TRUE; ``` Stats are usually logged locally and sent whenever log file exceeds 50Kb. Alternatively, one may disable automatic sending and instead use `usage-show` and `usage-send` commands to more carefully send data. ADDITIONAL CONFIGURATIONS FOR MAMP: ----------------------------------- Users of MAMP will need to manually specify in their PATH which version of php and MySQL to use in the command line interface. This is independent of the php version selected in the MAMP application settings. Under OS X, edit (or create if it does not already exist) a file called .bash_profile in your home folder. To use php 5.3.x, add this line to .bash_profile: export PATH="/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php5.3/bin:$PATH" If you want to use php 5.4.x, add this line instead: export PATH="/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php5.4/bin:$PATH" If you have MAMP v.1.84 or lower, this configuration will work for both version of PHP: export PATH="/Applications/MAMP/Library/bin:/Applications/MAMP/bin/php5/bin:$PATH" If you have done this and are still getting a "no such file or directory" error from PDO::__construct, try this: ```bash sudo mkdir /var/mysql sudo ln -s /Applications/MAMP/tmp/mysql/mysql.sock /var/mysql/mysql.sock ``` Additionally, you may need to adjust your php.ini settings before you can use drush successfully. See CONFIGURING PHP.INI below for more details on how to proceed. ADDITIONAL CONFIGURATIONS FOR OTHER AMP STACKS: ----------------------------------------------- Users of other Apache distributions such as XAMPP, or Acquia's Dev Desktop will want to ensure that its php can be found by the command line by adding it to the PATH variable, using the method in 3.b above. Depending on the version and distribution of your AMP stack, PHP might reside at: Path | Application ----- | ---- /Applications/acquia-drupal/php/bin | Acquia Dev Desktop (Mac) /Applications/xampp/xamppfiles/bin | XAMP (Mac) /opt/lampp/bin | XAMPP (Windows) Additionally, you may need to adjust your php.ini settings before you can use drush successfully. See CONFIGURING PHP.INI below for more details on how to proceed. RUNNING A SPECIFIC PHP FOR DRUSH -------------------------- If you want to run Drush with a specific version of php, rather than the php defined by your shell, you can add an environment variable to your the shell configuration file called .profile, .bash_profile, .bash_aliases, or .bashrc that is located in your home folder: export DRUSH_PHP='/path/to/php' CONFIGURING PHP.INI ------------------- Usually, php is configured to use separate php.ini files for the web server and the command line. Make sure that Drush's php.ini is given as much memory to work with as the web server is; otherwise, Drupal might run out of memory when Drush bootstraps it. To see which php.ini file Drush is using, run: $ drush status To see which php.ini file the webserver is using, use the phpinfo() function in a .php web page. See http://drupal.org/node/207036. If Drush is using the same php.ini file as the web server, you can create a php.ini file exclusively for Drush by copying your web server's php.ini file to the folder $HOME/.drush or the folder /etc/drush. Then you may edit this file and change the settings described above without affecting the php enviornment of your web server. Alternately, if you only want to override a few values, copy [example.drush.ini](examples/example.drush.ini) from the /examples folder into $HOME/.drush or the folder /etc/drush and edit to suit. See comments in example.drush.ini for more details. You may also use environment variables to control the php settings that Drush will use. There are three options: ```bash export PHP_INI='/path/to/php.ini' export DRUSH_INI='/path/to/drush.ini' export PHP_OPTIONS='-d memory_limit="128M"' ``` In the case of PHP_INI and DRUSH_INI, these environment variables specify the full path to a php.ini or drush.ini file, should you wish to use one that is not in one of the standard locations described above. The PHP_OPTIONS environment variable can be used to specify individual options that should be passed to php on the command line when Drush is executed. Drush requires a fairly unrestricted php environment to run in. In particular, you should insure that safe_mode, open_basedir, disable_functions and disable_classes are empty. If you are using php 5.3.x, you may also need to add the following definitions to your php.ini file: ```ini magic_quotes_gpc = Off magic_quotes_runtime = Off magic_quotes_sybase = Off ``` INSTALLING DRUSH ON WINDOWS: ---------------------------- Windows support has improved, but is still lagging. For full functionality, consider using on Linux/Unix/OSX using Virtualbox or other virtual machine. There is a Windows msi installer for drush available at http://www.drush.org/drush_windows_installer. Please see that page for more information on running Drush on Windows. Whenever the documentation or the help text refers to 'drush [option] ' or something similar, 'drush' may need to be replaced by 'drush.bat'. Additional Drush Windows installation documentation can be found at http://drupal.org/node/594744. Most Drush commands will run in a Windows CMD shell or PowerShell, but the Git Bash shell provided by the 'Git for Windows' installation is the preferred shell in which to run Drush commands. For more information on "Git for Windows' visit http://msysgit.github.com/. When creating aliases for Windows remote machines, pay particular attention to information presented in the example.aliases.drushrc.php file, especially when setting values for 'remote-host' and 'os', as these are very important when running Drush rsync and Drush sql-sync commands. OPTIONS ----------- For multisite installations, use the -l option to target a particular site. If you are outside the Drupal web root, you might need to use the -r, -l or other command line options just for Drush to work. If you do not specify a URI with -l and Drush falls back to the default site configuration, Drupal's $GLOBAL['base_url'] will be set to http://default. This may cause some functionality to not work as expected. $ drush -l http://example.com pm-update Related Options: ``` -r , --root= Drupal root directory to use (defaults to current directory or anywhere in a Drupal directory tree) -l , --uri= URI of the Drupal site to use -v, --verbose Display verbose output. ``` Very intensive scripts can exhaust your available PHP memory. One remedy is to just restart automatically using bash. For example: while true; do drush search-index; sleep 5; done DRUSH CONFIGURATION FILES ----------- Inside the [examples](examples) directory you will find some example files to help you get started with your Drush configuration file (example.drushrc.php), site alias definitions (example.aliases.drushrc.php) and Drush commands (sandwich.drush.inc). You will also see an example 'policy' file which can be customized to block certain commands or arguments as required by your organization's needs. DRUSHRC.PHP ----------- If you get tired of typing options all the time you can contain them in a drushrc.php file. Multiple Drush configuration files can provide the flexibility of providing specific options in different site directories of a multi-site installation. See [example.drushrc.php](examples/example.drushrc.php) for examples and installation details. SITE ALIASES ------------ Drush lets you run commands on a remote server, or even on a set of remote servers. Once defined, aliases can be references with the @ nomenclature, i.e. ```bash # Synchronize staging files to production $ drush rsync @staging:%files/ @live:%files # Syncronize database from production to dev, excluding the cache table $ drush sql-sync --structure-tables-key=custom --no-cache @live @dev ``` See http://drupal.org/node/670460 and [example.aliases.drushrc.php](examples/example.aliases.drushrc.php) for more information. COMMANDS -------- Drush can be extended to run your own commands. Writing a Drush command is no harder than writing simple Drupal modules, since they both follow the same structure. See [sandwich.drush.inc](examples/sandwich.drush.inc) for a quick tutorial on Drush command files. Otherwise, the core commands in Drush are good models for your own commands. You can put your Drush command file in a number of places: 1. In a folder specified with the --include option (see `drush topic docs-configuration`). 1. Along with one of your enabled modules. If your command is related to an existing module, this is the preferred approach. 1. In a .drush folder in your HOME folder. Note, that you have to create the .drush folder yourself. 1. In the system-wide Drush commands folder, e.g. /usr/share/drush/commands. 1. In Drupal's /drush or sites/all/drush folders. Note, that you have to create the drush folder yourself. In any case, it is important that you end the filename with ".drush.inc", so that Drush can find it. FAQ ------ ``` Q: What does "drush" stand for? A: The Drupal Shell. Q: How do I pronounce Drush? A: Some people pronounce the dru with a long u like Drupal. Fidelity points go to them, but they are in the minority. Most pronounce Drush so that it rhymes with hush, rush, flush, etc. This is the preferred pronunciation. Q: Does Drush have unit tests? A: Drush has an excellent suite of unit tests. See the README.md file in the /tests subdirectory for more information. ``` CREDITS ----------- * Originally developed by [Arto Bendiken](http://bendiken.net) for Drupal 4.7. * Redesigned by [Franz Heinzmann](http://unbiskant.org) in May 2007 for Drupal 5. * Maintained by [Moshe Weitzman](http://drupal.org/moshe) with much help from Owen Barton, greg.1.anderson, jonhattan, Mark Sonnabaum, and Jonathan Hedstrom. ![Drush Logo](drush_logo-black.png) drush-5.10.0/commands/000077500000000000000000000000001222105546100145215ustar00rootroot00000000000000drush-5.10.0/commands/core/000077500000000000000000000000001222105546100154515ustar00rootroot00000000000000drush-5.10.0/commands/core/archive.drush.inc000066400000000000000000000471141222105546100207200ustar00rootroot00000000000000 'Backup your code, files, and database into a single file.', 'arguments' => array( 'sites' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.', ), 'options' => array( 'description' => 'Describe the archive contents.', 'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.', 'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.', 'overwrite' => 'Do not fail if the destination file exists; overwrite it instead.', 'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".', 'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.', 'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.', 'preserve-symlinks' => 'Preserve symbolic links.', 'no-core' => 'Exclude Drupal core, so the backup only contains the site specific stuff.', 'tar-options' => 'Options passed thru to the tar command.', ), 'examples' => array( 'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.', 'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.', 'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.', 'drush archive-dump --tar-options="--exclude=.git --exclude=sites/default/files"' => 'Omits any .git directories found in the tree as well as sites/default/files.', 'drush archive-dump --tar-options="--exclude=%files"' => 'Placeholder %files is replaced with the real path for the current site, and that path is excluded.', ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, 'aliases' => array('ard', 'archive-backup', 'arb'), ); $items['archive-restore'] = array( 'description' => 'Expand a site archive into a Drupal web site.', 'arguments' => array( 'file' => 'The site archive file that should be expanded.', 'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.', ), 'required-arguments' => 1, 'options' => array( 'destination' => 'Specify where the Drupal site should be expanded, including the docroot. Defaults to the current working directory.', 'db-prefix' => 'An optional table prefix to use during restore.', 'db-url' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.', 'db-su' => 'Account to use when creating the new database. Optional.', 'db-su-pw' => 'Password for the "db-su" account. Optional.', 'overwrite' => 'Allow drush to overwrite any files in the destination.', ), 'examples' => array( 'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.', 'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.', 'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.', 'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).', ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'aliases' => array('arr'), ); return $items; } /** * Command callback. Generate site archive file. */ function drush_archive_dump($sites_subdirs = '@self') { $include_platform = !drush_get_option('no-core', FALSE); $tar = drush_get_tar_executable(); $sites = array(); $aliases = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs)); foreach ($aliases as $key => $alias) { $sites[$key] = $alias; if (($db_record = sitealias_get_databases_from_record($alias))) { $sites[$key]['databases'] = $db_record; } else { $sites[$key]['databases'] = array(); drush_log(dt('DB definition not found for !alias', array('!alias' => $key)), 'notice'); } } // The user can specify a destination filepath or not. That filepath might // end with .gz, .tgz, or something else. At the end of this command we will // gzip a file, and we want it to end up with the user-specified name (if // any), but gzip renames files and refuses to compress files ending with // .gz and .tgz, making our lives difficult. Solution: // // 1. Create a unique temporary base name to which gzip WILL append .gz. // 2. If no destination is provided, set $dest_dir to a backup directory and // $final_destination to be the unique name in that dir. // 3. If a destination is provided, set $dest_dir to that directory and // $final_destination to the exact name given. // 4. Set $destination, the actual working file we will build up, to the // unqiue name in $dest_dir. // 5. After gzip'ing $destination, rename $destination.gz to // $final_destination. // // Sheesh. // Create the unique temporary name. $prefix = 'none'; if (!empty($sites)) { $first = current($sites); if ( !empty($first['databases']['default']['default']['database']) ) { $prefix = count($sites) > 1 ? 'multiple_sites' : str_replace('/', '-', $first['databases']['default']['default']['database']); } } $date = gmdate('Ymd_His'); $temp_dest_name = "$prefix.$date.tar"; $final_destination = drush_get_option('destination'); if (!$final_destination) { // No destination provided. $backup = drush_include_engine('version_control', 'backup'); // TODO: this standard drush pattern leads to a slightly obtuse directory structure. $dest_dir = $backup->prepare_backup_dir('archive-dump'); if (empty($dest_dir)) { $dest_dir = drush_tempdir(); } $final_destination = "$dest_dir/$temp_dest_name.gz"; } else { // Use the supplied --destination. If it is relative, resolve it // relative to the directory in which drush was invoked. $command_cwd = getcwd(); drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd())); // This doesn't perform realpath on the basename, but that's okay. This is // not path-based security. We just use it for checking for perms later. drush_mkdir(dirname($final_destination)); $dest_dir = realpath(dirname($final_destination)); $final_destination = $dest_dir . '/' . basename($final_destination); drush_op('chdir', $command_cwd); } // $dest_dir is either the backup directory or specified directory. Set our // working file. $destination = "$dest_dir/$temp_dest_name"; // Validate the FINAL destination. It should be a file that does not exist // (unless --overwrite) in a writable directory (and a writable file if // it exists). We check all this up front to avoid failing after a long // dump process. $overwrite = drush_get_option('overwrite'); $dest_dir = dirname($final_destination); $dt_args = array('!file' => $final_destination, '!dir' => $dest_dir); if (is_dir($final_destination)) { drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('destination !file must be a file, not a directory.', $dt_args)); return; } else if (file_exists($final_destination)) { if (!$overwrite) { drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('destination !file exists; specify --overwrite to overwrite.', $dt_args)); return; } else if (!is_writable($final_destination)) { drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('destination !file is not writable.', $dt_args)); return; } } else if (!is_writable($dest_dir)) { drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('destination directory !dir is not writable.', $dt_args)); return; } // Get the extra options for tar, if any $tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', '')); // Start adding codebase to the archive. $docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT')); $docroot = basename($docroot_path); $workdir = dirname($docroot_path); if ($include_platform) { $dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference '; // Convert destination path to Unix style for tar on MinGW - see http://drupal.org/node/1844224 if (drush_is_mingw()) { $destination_orig = $destination; $destination = str_replace('\\', '/', $destination); $destination = preg_replace('$^([a-zA-Z]):$', '/$1', $destination); } // Archive Drupal core, excluding sites dir. drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot); // Add sites/all to the same archive. drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all"); // Add special files in sites/ to the archive. $files_to_add = array('sites/README.txt', 'sites/sites.php', 'sites/example.sites.php'); foreach ($files_to_add as $file_to_add) { if (file_exists($file_to_add)) { drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, $docroot . '/' . $file_to_add); } } } $tmp = drush_tempdir(); $all_dbs = array(); // Dump the default database for each site and add to the archive. foreach ($sites as $key => $alias) { if (isset($alias['databases']['default']['default'])) { $db = $alias['databases']['default']['default']; // Use a subdirectory name matching the docroot name. drush_mkdir("{$tmp}/{$docroot}"); // Ensure uniqueness by prefixing key if needed. Remove path delimiters. $dbname = str_replace(DIRECTORY_SEPARATOR, '-', $db['database']); $result_file = count($sites) == 1 ? "$tmp/$dbname.sql" : str_replace('@', '', "$tmp/$key-$dbname.sql"); $all_dbs[$key] = array( 'file' => basename($result_file), 'driver' => $db['driver'], ); $table_selection = drush_sql_get_table_selection(); list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $db, $result_file); if (!drush_shell_exec($dump_exec)) { return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed.'); } drush_shell_cd_and_exec($tmp, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, basename($result_file)); } } // Build a manifest file AND add sites/$subdir to archive as we go. $platform = array( 'datestamp' => time(), 'formatversion' => '1.0', 'generator' => drush_get_option('generator', 'Drush archive-dump'), 'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION), 'description' => drush_get_option('description', ''), 'tags' => drush_get_option('tags', ''), 'archiveformat' => ($include_platform ? 'platform' : 'site'), ); $contents = drush_export_ini(array('Global' => $platform)); $i=0; foreach ($sites as $key => $alias) { if (!$status = drush_invoke_process($alias, 'core-status', array(), array())) { drush_log(dt('Unable to determine sites directory for !alias', array('!alias' => $key)), 'warning'); } // Add the site specific directory to archive. if (!empty($status['object']['%paths']['%site'])) { drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, "{$docroot}/sites/" . basename($status['object']['%paths']['%site'])); } $site = array( 'docroot' => DRUPAL_ROOT, 'sitedir' => @$status['object']['%paths']['%site'], 'files-public' => @$status['object']['%paths']['%files'], 'files-private' => @$status['object']['%paths']['%private'], ); $site["database-default-file"] = $all_dbs[$key]['file']; $site["database-default-driver"] = $all_dbs[$key]['driver']; // The section title is the sites subdirectory name. $info[basename($site['sitedir'])] = $site; $contents .= "\n" . drush_export_ini($info); unset($info); $i++; } file_put_contents("{$tmp}/MANIFEST.ini", $contents); // Add manifest to archive. drush_shell_cd_and_exec($tmp, "$tar --dereference -rf %s %s", $destination, 'MANIFEST.ini'); // Ensure that default/default.settings.php is in the archive. This is needed // by site-install when restoring a site without any DB. // NOTE: Windows tar file replace operation is broken so we have to check if file already exists. // Otherwise it will corrupt the archive. $res = drush_shell_cd_and_exec($workdir, "$tar -tf %s %s", $destination, $docroot . '/sites/default/default.settings.php'); $output = drush_shell_exec_output(); if (!$res || !isset($output[0]) || empty($output[0])) { drush_shell_cd_and_exec($workdir, "$tar --dereference -vrf %s %s", $destination, $docroot . '/sites/default/default.settings.php'); } // Switch back to original destination in case it was modified for tar on MinGW. if (!empty($destination_orig)) { $destination = $destination_orig; } // Compress the archive drush_shell_exec("gzip --no-name -f %s", $destination); // gzip appends .gz unless the name already ends in .gz, .tgz, or .taz. if ("{$destination}.gz" != $final_destination) { drush_move_dir("{$destination}.gz", $final_destination, $overwrite); } drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), 'ok'); drush_print_pipe($final_destination); return $final_destination; } /** * Command argument complete callback. * * @return * List of site names/aliases for archival. */ function archive_archive_dump_complete() { return array('values' => array_keys(_drush_sitealias_all_list())); } /** * Command callback. Restore web site(s) from a site archive file. */ function drush_archive_restore($file, $site_id = NULL) { $tmp = drush_tempdir(); if (!$files = drush_tarball_extract($file, $tmp)) { return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp))); } $manifest = $tmp . '/MANIFEST.ini'; if (file_exists($manifest)) { if (!$ini = parse_ini_file($manifest, TRUE)) { return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.')); } } else { $ini = drush_archive_guess_manifest($tmp); } // Backward compatibility: 'archiveformat' did not exist // in older versions of archive-dump. if (!isset( $ini['Global']['archiveformat'])) { $ini['Global']['archiveformat'] = 'platform'; } // Grab the first site in the Manifest and move docroot to destination. $ini_tmp = $ini; unset($ini_tmp['Global']); $first = array_shift($ini_tmp); $docroot = basename($first['docroot']); $destination = drush_get_option('destination', realpath('.') . "/$docroot"); if ($ini['Global']['archiveformat'] == 'platform') { // Move the whole platform inplace at once. if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) { return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore platform to !dest', array('!dest' => $destination))); } } else { // When no platform is included we do this on a per-site basis. } // Loop over sites and restore databases and append to settings.php. foreach ($ini as $section => $site) { if ($section != 'Global' && (is_null($site_id) || $section == $site_id) && !empty($site['database-default-file'])) { $site_destination = $destination . '/' . $site['sitedir']; // Restore site, in case not already done above. if ($ini['Global']['archiveformat'] == 'site') { if (!drush_move_dir("$tmp/$docroot/" . $site['sitedir'], $site_destination, drush_get_option('overwrite'))) { return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore site to !dest', array('!dest' => $site_destination))); } } // Restore database. $sql_file = $tmp . '/' . $site['database-default-file']; if ($db_url = drush_get_option('db-url')) { if (empty($site_id) && count($ini) >= 3) { // TODO: Use drushrc to provide multiple db-urls for multi-restore? return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.')); } $db_spec = drush_convert_db_from_db_url($db_url); } else { $site_specification = $destination . '#' . $section; if ($return = drush_invoke_process($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) { $databases = $return['object']; $db_spec = $databases['default']['default']; } else { return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key))); } } drush_sql_empty_db($db_spec); _drush_sql_query(NULL, $db_spec, $sql_file); // Append new DB info to settings.php. if ($db_url) { $settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php'; //If settings.php doesn't exist in the archive, create it from default.settings.php. if (!file_exists($settingsfile)) { drush_op('copy', $destination . '/sites/default/default.settings.php', $settingsfile); } // Need to do something here or else we can't write. chmod($settingsfile, 0664); file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND); if (drush_drupal_major_version($destination) >= 7) { file_put_contents($settingsfile, "\n" . '$databases = ' . var_export(drush_sitealias_convert_db_from_db_url($db_url), TRUE) . ";\n", FILE_APPEND); } else { file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND); } drush_log(dt('Drush appended the new database configuration at settings.php. Optionally remove the old configuration manually.'), 'ok'); } } } drush_log(dt('Archive restored to !dest', array('!dest' => $destination)), 'ok'); return $destination; } /** * Command argument complete callback. * * @return * Strong glob of files to complete on. */ function archive_archive_restore_complete() { return array( 'files' => array( 'directories' => array( 'pattern' => '*', 'flags' => GLOB_ONLYDIR, ), 'tar' => array( 'pattern' => '*.tar.gz', ), ), ); } /** * Try to find docroot and DB dump file in an extracted archive. * * @param string $path The location of the extracted archive. * @return array The manifest data. */ function drush_archive_guess_manifest($path) { $db_file = drush_scan_directory($path, '/\.sql$/', array('.', '..', 'CVS'), 0, 0); if (file_exists($path . '/index.php')) { $docroot = './'; } else { $directories = glob($path . '/*' , GLOB_ONLYDIR); $docroot = reset($directories); } $ini = array( 'Global' => array( // Very crude detection of a platform... 'archiveformat' => (drush_drupal_version($docroot) ? 'platform' : 'site'), ), 'default' => array( 'docroot' => $docroot, 'sitedir' => 'sites/default', 'database-default-file' => key($db_file), ), ); return $ini; } drush-5.10.0/commands/core/cache.drush.inc000066400000000000000000000176071222105546100203460ustar00rootroot00000000000000 'Fetch a cached object and display it.', 'examples' => array( 'drush cache-get schema' => 'Display the data for the cache id "schema" from the "cache" bin.', 'drush cache-get update_available_releases update' => 'Display the data for the cache id "update_available_releases" from the "update" bin.', ), 'arguments' => array( 'cid' => 'The id of the object to fetch.', 'bin' => 'Optional. The cache bin to fetch from.', ), 'required-arguments' => 1, 'options' => array( 'format' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.', ), 'callback' => 'drush_cache_command_get', 'aliases' => array('cg'), ); $items['cache-clear'] = array( 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'description' => 'Clear a specific cache, or all drupal caches.', 'arguments' => array( 'type' => 'The particular cache to clear. Omit this argument to choose from available caches.', ), 'callback' => 'drush_cache_command_clear', 'aliases' => array('cc'), ); $items['cache-set'] = array( 'description' => 'Cache an object expressed in JSON or var_export() format.', 'arguments' => array( 'cid' => 'The id of the object to set.', 'data' => 'The object to set in the cache. Use \'-\' to read the object from STDIN.', 'bin' => 'Optional. The cache bin to store the object in.', 'expire' => 'Optional. CACHE_PERMANENT, CACHE_TEMPORARY, or a Unix timestamp.', ), 'required-arguments' => 2, 'options' => array( 'format' => 'Format to parse the object. Use "string" for string (default), and "json" for JSON.', 'cache-get' => 'If the object is the result a previous fetch from the cache, only store the value in the "data" property of the object in the cache.', ), 'callback' => 'drush_cache_command_set', 'aliases' => array('cs'), ); return $items; } /** * Command argument complete callback. * * @return * Array of clear types. */ function cache_cache_command_clear_complete() { return array('values' => array_keys(drush_cache_clear_types(TRUE))); } /** * Command callback for drush cache-clear. */ function drush_cache_command_clear($type = NULL) { $types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)); // Check if the provided type ($type) is a valid cache type. if ($type && !key_exists($type, $types)) { // If we haven't done a full bootstrap, provide a more // specific message with instructions to the user on // bootstrapping a Drupal site for more options. if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { $all_types = drush_cache_clear_types(TRUE); if (key_exists($type, $all_types)) { return drush_set_error(dt("'!type' cache requires a working Drupal site to operate on. Use the --root and --uri options, or a site @alias, or cd to a directory containing a Drupal settings.php file.", array('!type' => $type))); } else { return drush_set_error(dt("'!type' cache is not a valid cache type. There may be more cache types available if you select a working Drupal site.", array('!type' => $type))); } } return drush_set_error(dt("'!type' cache is not a valid cache type.", array('!type' => $type))); } if ($type) { drush_op($types[$type]); if ($type == 'all' && !drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { $type = 'drush'; } } else { // Don't offer 'all' unless Drush has bootstrapped the Drupal site if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { unset($types['all']); } $type = drush_choice($types, 'Enter a number to choose which cache to clear.', '!key'); if ($type !== FALSE) { call_user_func($types[$type]); } } if ($type !== FALSE) { $site_label = ''; if ($type != 'drush') { $self_name = drush_sitealias_bootstrapped_site_name(); if (isset($self_name)) { $site_label = dt(' in !name', array('!name' => $self_name)); } } drush_log(dt("'!name' cache was cleared!insitename", array('!name' => $type, '!insitename' => $site_label)), 'success'); } } /** * Print an object returned from the cache. * * @param $cid * The cache ID of the object to fetch. * @param $bin * Optional parameter to specify a specific bin to fetch from. */ function drush_cache_command_get($cid = NULL, $bin = NULL) { if (!$cid) { drush_log(dt('You must specify a cache id to fetch.'), 'error'); return; } if (!$bin) { $bin = 'cache'; } $result = cache_get($cid, $bin); if (!empty($result)) { drush_print(drush_format($result)); } else { drush_log(dt('The !cid object in the !bin cache bin was not found.', array('!cid' => $cid, '!bin' => $bin)), 'error'); } } /** * Set an object in the cache. * * @param $cid * The cache ID of the object to fetch. * @param $data * The data to save to the cache, or '-' to read from STDIN. * @param $bin * Optional parameter to specify a specific bin to fetch from. * @param $expire * Optional parameter to specify the expiry of the cached object. */ function drush_cache_command_set($cid = NULL, $data = '', $bin = NULL, $expire = CACHE_PERMANENT) { if (!$bin) { $bin = 'cache'; } if ($data == '-') { $data = stream_get_contents(STDIN); } // Now, we parse the object. switch (drush_get_option('format', 'string')) { case 'json': $data = drush_json_decode($data); break; } if (drush_get_option('cache-get')) { $data = $data->data; } cache_set($cid, $data, $bin, $expire); } /* * All types of caches available for clearing. Contrib commands can alter in their own. */ function drush_cache_clear_types($include_bootstraped_types = FALSE) { $types = array( 'all' => 'drush_cache_clear_both', 'drush' => 'drush_cache_clear_drush', ); if ($include_bootstraped_types) { $types += array( 'theme-registry' => 'drush_cache_clear_theme_registry', 'menu' => 'menu_rebuild', 'css-js' => 'drush_cache_clear_css_js', 'block' => 'drush_cache_clear_block', 'module-list' => 'drush_get_modules', 'theme-list' => 'drush_get_themes', ); } if (drush_drupal_major_version() >= 7) { $types['registry'] = 'registry_update'; } elseif (drush_drupal_major_version() == 6 && function_exists('module_exists') && module_exists('autoload')) { // TODO: move this to autoload module. $types['registry'] = 'autoload_registry_update'; } // Include the appropriate environment engine, so callbacks can use core // version specific cache clearing functions directly. drush_include_engine('drupal', 'environment'); // Command files may customize $types as desired. drush_command_invoke_all_ref('drush_cache_clear', $types); return $types; } function drush_cache_clear_theme_registry() { if (drush_drupal_major_version() >= 7) { drupal_theme_rebuild(); } else { cache_clear_all('theme_registry', 'cache', TRUE); } } function drush_cache_clear_css_js() { _drupal_flush_css_js(); drupal_clear_css_cache(); drupal_clear_js_cache(); } /** * Clear the cache of the block output. */ function drush_cache_clear_block() { cache_clear_all(NULL, 'cache_block'); } /** * Clear caches internal to drush core. */ function drush_cache_clear_drush() { drush_cache_clear_all(NULL, 'default'); // commandfiles, etc. drush_cache_clear_all(NULL, 'complete'); // completion } /** * Clear caches internal to Drush core and Drupal. */ function drush_cache_clear_both() { drush_cache_clear_drush(); if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { drupal_flush_all_caches(); } } drush-5.10.0/commands/core/core.drush.inc000066400000000000000000001401741222105546100202270ustar00rootroot00000000000000 'Print this help message. See `drush help help` for more options.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'allow-additional-options' => TRUE, 'options' => array( 'sort' => 'Sort commands in alphabetical order. drush waits for full bootstrap before printing any commands when this option is used.', 'filter' => array( 'description' => 'Restrict command list to those commands defined in the specified file. Omit value to choose from a list of names.', 'example-value' => 'category', 'value' => 'optional', ), 'format' => 'Format to output . Allowed values are: json, export, html.', 'html' => 'Print help for all commands in HTML format. Deprecated - see --format option.', 'pipe' => 'A list of available commands, one per line.', ), 'arguments' => array( 'command' => 'A command name, or command alias.', ), 'examples' => array( 'drush' => 'List all commands.', 'drush --filter=devel_generate' => 'Show only commands defined in devel_generate.drush.inc', 'drush help pm-download' => 'Show help for one command.', 'drush help dl' => 'Show help for one command using an alias.', ), 'topics' => array('docs-readme'), ); $items['version'] = array( 'description' => 'Show drush version.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'options' => array( 'pipe' => 'Print just the version number, and nothing else.', 'self-update' => 'Check for pending updates to Drush itself. Set to 0 to disable.', ), ); $items['self-update'] = array( 'description' => 'Check to see if there is a newer Drush release available.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'aliases' => array('selfupdate'), ); $items['core-cron'] = array( 'description' => 'Run all cron hooks in all active modules for specified site.', 'aliases' => array('cron'), 'topics' => array('core-cron'), ); $items['updatedb'] = array( 'description' => 'Apply any database updates required (as with running update.php).', 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, 'aliases' => array('updb'), ); $items['core-config'] = array( 'description' => 'Edit drushrc, site alias, and Drupal settings.php files.', 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'arguments' => array( 'filter' => 'A substring for filtering the list of files. Omit this argument to choose from loaded files.', ), 'options' => array( 'bg' => 'Run editor in the background. Does not work with editors such as `vi` that run in the terminal.', ), 'examples' => array( 'drush core-config' => 'Pick from a list of config/alias/settings files. Open selected in editor.', 'drush --bg core-config' => 'Return to shell prompt as soon as the editor window opens.', 'drush core-config etc' => 'Edit the global configuration file.', 'drush core-config demo.alia' => 'Edit a particular alias file.', 'drush core-config sett' => 'Edit settings.php for the current Drupal site.', 'drush core-config --choice=2' => 'Edit the second file in the choice list.', ), 'aliases' => array('conf', 'config'), ); $items['core-status'] = array( 'description' => 'Provides a birds-eye view of the current Drupal installation, if any.', 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'aliases' => array('status', 'st'), 'examples' => array( 'drush core-status version' => 'Show all status lines that contain version information.', 'drush core-status --pipe' => 'A list key=value items separated by line breaks.', 'drush core-status drush-version --pipe' => 'Emit just the drush version with no label.', ), 'arguments' => array( 'item' => 'Optional. The status item line(s) to display.', ), 'options' => array( 'show-passwords' => 'Show database password.', 'full' => 'Show all drush aliases in the report, even if there are a lot.', 'project' => array( 'description' => 'One or more projects that should be added to the path list', 'example-value' => 'foo,bar', ), ), 'topics' => array('docs-readme'), ); $items['core-requirements'] = array( 'description' => 'Provides information about things that may be wrong in your Drupal installation, if any.', 'aliases' => array('status-report','rq'), 'examples' => array( 'drush core-requirements' => 'Show all status lines from the Status Report admin page.', 'drush core-requirements --severity=2' => 'Show only the red lines from the Status Report admin page.', 'drush core-requirements --pipe' => 'Print out a short report in the format "identifier: severity", where severity 2=error, 1=warning, and 0/-1=OK', ), 'options' => array( 'severity' => array( 'description' => 'Only show status report messages with a severity greater than or equal to the specified value.', 'value' => 'required', 'example-value' => '3', ), 'ignore' => 'Comma-separated list of requirements to remove from output. Run with --pipe to see key values to use.', ), ); $items['php-eval'] = array( 'description' => 'Evaluate arbitrary php code after bootstrapping Drupal (if available).', 'examples' => array( 'drush php-eval "variable_set(\'hello\', \'world\');"' => 'Sets the hello variable using Drupal API.', ), 'arguments' => array( 'code' => 'PHP code', ), 'required-arguments' => TRUE, 'allow-additional-options' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'aliases' => array('eval', 'ev'), ); $items['php-script'] = array( 'description' => "Run php script(s).", 'examples' => array( 'drush php-script scratch' => 'Run scratch.php script. See commands/core directory.', 'drush php-script example --script-path=/path/to/scripts:/another/path' => 'Run script from specified paths', 'drush php-script' => 'List all available scripts.', '' => '', "#!/usr/bin/env drush\n "Execute php code with a full Drupal bootstrap directly from a shell script.", ), 'arguments' => array( 'filename' => 'Optional. The file you wish to execute (without extension). If omitted, list files ending in .php in the current working directory and specified script-path. Some might not be real drush scripts. Beware.', ), 'options' => array( 'script-path' => array( 'description' => "Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).", 'example-value' => '~/scripts', ), ), 'allow-additional-options' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'aliases' => array('scr'), 'topics' => array('docs-examplescript', 'docs-scripts'), ); $items['core-execute'] = array( 'description' => 'Execute a shell command. Usually used with a site alias.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'arguments' => array( 'command' => 'The shell command to be executed.', ), 'options' => drush_shell_exec_proc_build_options(), 'required-arguments' => TRUE, 'allow-additional-options' => TRUE, 'handle-remote-commands' => TRUE, 'strict-option-handling' => TRUE, 'examples' => array( 'drush core-execute git pull origin rebase' => 'Retrieve latest code from git', ), 'aliases' => array('exec', 'execute'), 'topics' => array('docs-aliases'), ); $items['core-rsync'] = array( 'description' => 'Rsync the Drupal tree to/from another server using ssh.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. 'arguments' => array( 'source' => 'May be rsync path or site alias. See rsync documentation and example.aliases.drushrc.php.', 'destination' => 'May be rsync path or site alias. See rsync documentation and example.aliases.drushrc.php.', ), 'options' => array( 'mode' => 'The unary flags to pass to rsync; --mode=rultz implies rsync -rultz. Default is -akz.', 'exclude-conf' => 'Excludes settings.php from being rsynced. Default.', 'include-conf' => 'Allow settings.php to be rsynced. Default is to exclude settings.php.', 'include-vcs' => 'Include special version control directories (e.g. .svn). Default is to exclude vcs files.', 'exclude-files' => 'Exclude the files directory.', 'exclude-sites' => 'Exclude all directories in "sites/" except for "sites/all".', 'exclude-other-sites' => 'Exclude all directories in "sites/" except for "sites/all" and the site directory for the site being synced. Note: if the site directory is different between the source and destination, use --exclude-sites followed by "drush rsync @from:%site @to:%site"', 'exclude-paths' => 'List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).', 'include-paths' => 'List of paths to include, seperated by : (Unix-based systems) or ; (Windows).', '{rsync-option-name}' => "Replace {rsync-option-name} with the rsync option (or option='value') that you would like to pass through to rsync. Examples include --delete, --exclude=*.sql, --filter='merge /etc/rsync/default.rules', etc. See the rsync documentation for a complete explaination of all the rsync options and values.", ), 'strict-option-handling' => TRUE, 'examples' => array( 'drush rsync @dev @stage' => 'Rsync Drupal root from Drush alias dev to the alias stage (one of which must be local).', 'drush rsync ./ @stage:%files/img' => 'Rsync all files in the current directory to the \'img\' directory in the file storage folder on the Drush alias stage.', 'drush -s rsync @dev @stage --exclude=*.sql --delete' => "Simulate Rsync Drupal root from the Drush alias dev to the alias stage (one of which must be local), excluding all files that match the filter '*.sql' and delete all files on the destination that are no longer on the source.", ), 'aliases' => array('rsync'), 'topics' => array('docs-aliases'), ); $items['site-install'] = array( 'description' => 'Install Drupal along with modules/themes/configuration using the specified install profile.', 'arguments' => array( 'profile' => 'the install profile you wish to run. defaults to \'default\' in D6, \'standard\' in D7+', 'key=value...' => 'any additional settings you wish to pass to the profile. Fully supported on D7+, partially supported on D6 (single step configure forms only). The key is in the form [form name].[parameter name] on D7 or just [parameter name] on D6.', ), 'options' => array( 'db-url' => array( 'description' => 'A Drupal 6 style database URL. Only required for initial install - not re-install.', 'example-value' => 'mysql://root:pass@127.0.0.1/db', ), 'db-prefix' => 'An optional table prefix to use for initial install. Can be a key-value array of tables/prefixes in a drushrc file (not the command line).', 'db-su' => array( 'description' => 'Account to use when creating a new database. Must have Grant permission (mysql only). Optional.', 'example-value' => 'root', ), 'db-su-pw' => array( 'description' => 'Password for the "db-su" account. Optional.', 'example-value' => 'pass', ), 'account-name' => 'uid1 name. Defaults to admin', 'account-pass' => 'uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drushrc.php.', 'account-mail' => 'uid1 email. Defaults to admin@example.com', 'locale' => array( 'description' => 'A short language code. Sets the default site language. Language files must already be present. You may use download command to get them.', 'example-value' => 'en-GB', ), 'clean-url'=> 'Defaults to 1', 'site-name' => 'Defaults to Site-Install', 'site-mail' => 'From: for system mailings. Defaults to admin@example.com', 'sites-subdir' => array( 'description' => "Name of directory under 'sites' which should be created. Only needed when the subdirectory does not already exist. Defaults to 'default'", 'value' => 'required', 'example-value' => 'directory_name', ), ), 'examples' => array( 'drush site-install expert --locale=uk' => '(Re)install using the expert install profile. Set default language to Ukranian.', 'drush site-install --db-url=mysql://root:pass@localhost:port/dbname' => 'Install using the specified DB params.', 'drush site-install --db-url=sqlite://sites/example.com/files/.ht.sqlite' => 'Install using SQLite (D7+ only).', 'drush site-install --account-name=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.', 'drush site-install standard install_configure_form.site_default_country=FR my_profile_form.my_settings.key=value' => 'Pass additional arguments to the profile (D7 example shown here - for D6, omit the form id).', "drush site-install install_configure_form.update_status_module='array(FALSE,FALSE)'" => 'Disable email notification during install and later. If your server has no smtp, this gets rid of an error during install.', ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, 'aliases' => array('si'), ); $items['drupal-directory'] = array( 'description' => 'Return path to a given module/theme directory.', 'arguments' => array( 'target' => 'A module/theme name, or special names like root, files, private, or an alias : path alias string such as @alias:%files. Defaults to root.', ), 'options' => array( 'component' => "The portion of the evaluated path to return. Defaults to 'path'; 'name' returns the site alias of the target.", 'local' => "Reject any target that specifies a remote site.", ), 'examples' => array( 'cd `drush dd devel`' => 'Navigate into the devel module directory', 'cd `drush dd` ' => 'Navigate to the root of your Drupal site', 'cd `drush dd files`' => 'Navigate to the files directory.', 'drush dd @alias:%files' => 'Print the path to the files directory on the site @alias.', 'edit `drush dd devel`/devel.module' => "Open devel module in your editor (customize 'edit' for your editor)", ), 'aliases' => array('dd'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, ); $items['batch-process'] = array( 'description' => 'Process operations in the specified batch set', 'hidden' => TRUE, 'arguments' => array( 'batch-id' => 'The batch id that will be processed.', ), 'required-arguments' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, ); $items['updatedb-batch-process'] = array( 'description' => 'Perform update functions', 'hidden' => TRUE, 'arguments' => array( 'batch-id' => 'The batch id that will be processed', ), 'required-arguments' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, ); $items['core-global-options'] = array( 'description' => 'All global options', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, ); $items['core-quick-drupal'] = array( 'description' => 'Download, install, serve and login to Drupal with minimal configuration and dependencies.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'aliases' => array('qd'), 'arguments' => array( 'site' => 'Short name for the site to be created - used as a directory name and as sqlite file name. Optional - if omitted timestamped "quick-drupal" directory will be used instead.', 'projects' => 'A list of projects to download into the new site. If projects contain extensions (modules or themes) with the same name they will be enabled by default. See --enable option to control this behaviour further.', ), 'examples' => array( 'drush qd' => 'Download and install stable release of Drupal into a timestamped directory, start server, and open the site logged in as admin.', 'drush qd --profile=minimal --dev --cache --core=drupal-8.x --yes' => 'Fire up dev release of Drupal site with minimal install profile.', 'drush qd testsite devel --server=:8081/admin --browser=firefox --cache --yes' => 'Fire up stable release (using the cache) of Drupal site called "testsite", download and enable devel module, start a server on port 8081 and open /admin in firefox.', 'drush qd commercesite --core=commerce_kickstart --profile=commerce_kickstart --cache --yes --watchdog' => 'Download and install the "Commerce Kickstart" distribution/install profile, display watchdog messages on the server console.', 'drush qd --makefile=mysite.make' => 'Create and install a site from a makefile.', ), ); // Add in options/engines. drush_core_quick_drupal_options($items); return $items; } /** * Command argument complete callback. * * @return * Array of available command names. */ function core_help_complete() { return array('values' => array_keys(drush_get_commands())); } /** * Command argument complete callback. * * @return * Array of available profile names. */ function core_site_install_complete() { $max = drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_ROOT); if ($max >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { return array('values' => array_keys(drush_find_profiles(DRUPAL_ROOT))); } } /** * Command argument complete callback. * * @return * Array of available site aliases. */ function core_core_rsync_complete() { return array('values' => array_keys(_drush_sitealias_all_list())); } /** * @defgroup engines Engine types * @{ */ /** * Implementation of hook_drush_engine_type_info(). */ function core_drush_engine_type_info() { return array( 'drupal' => array( ), ); } function core_drush_engine_drupal() { $engines = array(); $engines['batch'] = array(); $engines['update'] = array(); $engines['environment'] = array(); $engines['site_install'] = array(); return $engines; } /** * @} End of "Engine types". */ /** * Command handler. Execute update.php code from drush. */ function drush_core_updatedb() { if (drush_get_context('DRUSH_SIMULATE')) { drush_log(dt('updatedb command does not support --simulate option.'), 'ok'); return TRUE; } drush_include_engine('drupal', 'update', drush_drupal_major_version()); if (update_main() === FALSE) { return FALSE; } // Clear all caches in a new process. We just performed major surgery. drush_invoke_process('@self', 'cache-clear', array('all')); drush_log(dt('Finished performing updates.'), 'ok'); } /** * Implementation of hook_drush_help(). * * This function is called whenever a drush user calls * 'drush help ' * * @param * A string with the help section (prepend with 'drush:') * * @return * A string with the help text for your command. */ function core_drush_help($section) { switch ($section) { case 'meta:core:title': return dt("Core drush commands"); case 'drush:help': return dt("Drush provides an extensive help system that describes both drush commands and topics of general interest. Use `drush help --filter` to present a list of command categories to view, and `drush topic` for a list of topics that go more in-depth on how to use and extend drush."); case 'drush:php-script': return dt("Runs the given php script(s) after a full Drupal bootstrap. A useful alternative to eval command when your php is lengthy or you can't be bothered to figure out bash quoting. If you plan to share a script with others, consider making a full drush command instead, since that's more self-documenting. Drush provides commandline options to the script via drush_get_option('option-name'), and commandline arguments can be accessed either via drush_get_arguments(), which returns all arguments in an array, or drush_shift(), which removes the next argument from the list and returns it."); case 'drush:rsync': return dt("Sync the entire drupal directory or a subdirectory to a using ssh. Excludes reserved files and directories for supported VCSs. Useful for pushing copies of your tree to a staging server, or retrieving a files directory from a remote site. Relative paths start from the Drupal root directory if a site alias is used; otherwise they start from the current working directory."); case 'drush:drupal-directory': return dt("Return the filesystem path for modules/themes and other key folders."); case 'error:DRUSH_DRUPAL_DB_ERROR': $message = dt("Drush was not able to start (bootstrap) the Drupal database.\n"); $message .= dt("Hint: This may occur when Drush is trying to:\n"); $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"); $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"); $message .= dt("\nDrush was attempting to connect to: \n!credentials\n", array('!credentials' => _core_site_credentials())); return $message; case 'error:DRUSH_DRUPAL_BOOTSTRAP_ERROR': $message = dt("Drush was not able to start (bootstrap) Drupal.\n"); $message .= dt("Hint: This error can only occur once the database connection has already been successfully initiated, therefore this error generally points to a site configuration issue, and not a problem connecting to the database.\n"); $message .= dt("\nDrush was attempting to connect to: \n!credentials\n", array('!credentials' => _core_site_credentials())); return $message; break; } } // TODO: consolidate with SQL commands? function _core_site_credentials() { $status_table = _core_site_status_table(); return _core_site_credential_table($status_table); } function _core_site_credential_table($status_table) { $credentials = ''; foreach ($status_table as $key => $value) { $credentials .= sprintf(" %-18s: %s\n", $key, $value); } return $credentials; } function _core_site_credential_list($status_table) { $credentials = ''; foreach ($status_table as $key => $value) { if (isset($value)) { $credentials .= sprintf("%s=%s\n", strtolower(str_replace(' ', '_', $key)), $value); } } return $credentials; } function _core_path_aliases($project = '') { $paths = array(); if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { $paths['%root'] = $drupal_root; if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { $paths['%site'] = $site_root; if (is_dir($modules_path = conf_path() . '/modules')) { $paths['%modules'] = $modules_path; } else { $paths['%modules'] = 'sites/all/modules'; } if (is_dir($themes_path = conf_path() . '/themes')) { $paths['%themes'] = $themes_path; } else { $paths['%themes'] = 'sites/all/themes'; } if (drush_drupal_major_version() >= 7) { if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_SITE) { $paths['%files'] = variable_get('file_public_path', conf_path() . '/files'); $private_path = variable_get('file_private_path', FALSE); if (!empty($private_path)) { $paths['%private'] = $private_path; } } } elseif (function_exists('file_directory_path')) { $paths['%files'] = file_directory_path(); } if (function_exists('file_directory_temp')) { $paths['%temp'] = file_directory_temp(); } // If the 'project' parameter was specified, then search // for a project (or a few) and add its path to the path list if (!empty($project)) { foreach(explode(',', $project) as $target) { $path = drush_core_find_project_path($target); if(isset($path)) { $paths['%' . $target] = $path; } } } } } // Add in all of the global paths from $options['path-aliases'] $paths = array_merge($paths, drush_get_option('path-aliases', array())); return $paths; } function _core_site_status_table($project = '', $full = FALSE) { $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { $status_table['Drupal version'] = drush_drupal_version(); if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { $status_table['Site URI'] = drush_get_context('DRUSH_URI'); if ($creds = drush_get_context('DRUSH_DB_CREDENTIALS')) { $status_table['Database driver'] = $creds['driver']; if (!empty($creds['unix_socket'])) { $status_table['Database socket'] = $creds['unix_socket']; } else { $status_table['Database hostname'] = $creds['host']; } $status_table['Database username'] = $creds['user']; $status_table['Database name'] = $creds['name']; if (drush_get_option('show-passwords', FALSE)) { $status_table['Database password'] = $creds['pass']; } if ($phase > DRUSH_BOOTSTRAP_DRUPAL_DATABASE) { $status_table['Database'] = dt('Connected'); if ($phase > DRUSH_BOOTSTRAP_DRUPAL_FULL) { $status_table['Drupal bootstrap'] = dt('Successful'); if ($phase == DRUSH_BOOTSTRAP_DRUPAL_LOGIN) { global $user; $username = ($user->uid) ? $user->name : dt('Anonymous'); $status_table['Drupal user'] = $username; } } } } } $status_table['Default theme'] = drush_theme_get_default(); $status_table['Administration theme'] = drush_theme_get_admin(); } if ($php_ini_files = _drush_core_config_php_ini_files()) { $status_table['PHP configuration'] = implode(' ', $php_ini_files); } $status_table['Drush version'] = DRUSH_VERSION; $status_table['Drush configuration'] = implode(' ', drush_get_context_options('context-path', TRUE)); $alias_files = _drush_sitealias_find_alias_files(); if (!empty($alias_files)) { if ($full || count($alias_files) < 24) { $status_table['Drush alias files'] = implode(' ', $alias_files); } else { $status_table['Drush alias files'] = dt("There are !count alias files. Run with --full to see the full list.", array('!count' => count($alias_files))); } } // None of the Status keys are in dt(); this helps with machine-parsing of status? $path_names['root'] = 'Drupal root'; $path_names['site'] = 'Site path'; $path_names['modules'] = 'Modules path'; $path_names['themes'] = 'Themes path'; $path_names['files'] = 'File directory path'; $path_names['private'] = 'Private file directory path'; $path_names['temp'] = 'Temporary file directory path'; $paths = _core_path_aliases($project); if (!empty($paths)) { foreach ($paths as $target => $one_path) { $name = $target; if (substr($name,0,1) == '%') { $name = substr($name,1); } if (array_key_exists($name, $path_names)) { $name = $path_names[$name]; } $status_table[$name] = $one_path; } } // Store the paths into the '%paths' index; this will be // used by other code, but will not be included in the output // of the drush status command. $status_table['%paths'] = $paths; return $status_table; } /** * Command callback. Runs all cron hooks. */ function drush_core_cron() { if (drupal_cron_run()) { drush_log(dt('Cron run successful.'), 'success'); } else { return drush_set_error('DRUSH_CRON_FAILED', dt('Cron run failed.')); } } /** * Command callback. Edit drushrc and alias files. */ function drush_core_config($filter = NULL) { $all = drush_core_config_load(); // Run in the foreground unless --bg is specified. $bg = ''; if (drush_get_option('bg', FALSE)) { $bg = '&'; } // Apply any filter that was supplied. if ($filter) { foreach ($all as $key => $file) { if (strpos($file, $filter) === FALSE) { unset($all[$key]); } } } $all = drush_map_assoc(array_values($all)); $exec = drush_get_editor(); if (count($all) == 1) { $filepath = current($all); return drush_shell_exec_interactive($exec, $filepath, $filepath); } else { $choice = drush_choice($all, 'Enter a number to choose which file to edit.', '!key'); if ($choice !== FALSE) { $filepath = $all[$choice]; drush_shell_exec_interactive($exec, $filepath, $filepath); } } } /** * Command argument complete callback. * * @return * Array of available configuration files for editing. */ function core_core_config_complete() { return array('values' => drush_core_config_load(FALSE)); } function drush_core_config_load($headers = TRUE) { $php_header = $php = $rcs_header = $rcs = $aliases_header = $aliases = $drupal_header = $drupal = array(); $php = _drush_core_config_php_ini_files(); if (!empty($php)) { if ($headers) { $php_header = array('phpini' => '-- PHP ini files --'); } } drush_sitealias_load_all(); if ($rcs = drush_get_context_options('context-path', TRUE)) { if ($headers) { $rcs_header = array('drushrc' => '-- Drushrc --'); } } if ($aliases = drush_get_context('drush-alias-files')) { if ($headers) { $aliases_header = array('aliases' => '-- Aliases --'); } } if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { $drupal = array_merge(array(realpath($site_root . '/settings.php'), realpath(DRUPAL_ROOT . '/.htaccess'))); if ($headers) { $drupal_header = array('drupal' => '-- Drupal --'); } } return array_merge($php_header, $php, $rcs_header, $rcs, $aliases_header, $aliases, $drupal_header, $drupal); } function _drush_core_config_php_ini_files() { $ini_files = array(); // Function available on PHP >= 5.2.4, but we use it if available to help // users figure out their php.ini issues. if (function_exists('php_ini_loaded_file')) { $ini_files[] = php_ini_loaded_file(); } foreach (array(DRUSH_BASE_PATH, '/etc/drush', drush_server_home() . '/.drush') as $ini_dir) { if (file_exists($ini_dir . "/php.ini")) { $ini_files[] = realpath($ini_dir . "/php.ini"); } if (file_exists($ini_dir . "/drush.ini")) { $ini_files[] = realpath($ini_dir . "/drush.ini"); } } return $ini_files; } /** * Command callback. Provides information from the 'Status Reports' admin page. */ function drush_core_requirements() { include_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; $severities = array( REQUIREMENT_INFO => t('Info'), REQUIREMENT_OK => t('OK'), REQUIREMENT_WARNING => t('Warning'), REQUIREMENT_ERROR => t('Error'), ); drupal_load_updates(); $requirements = module_invoke_all('requirements', 'runtime'); $ignore_requirements = drush_get_option_list('ignore'); foreach ($ignore_requirements as $ignore) { unset($requirements[$ignore]); } ksort($requirements); $min_severity = drush_get_option('severity', -1); $rows[] = array('Title', 'Severity', 'Description'); foreach($requirements as $key => $info) { $severity = array_key_exists('severity', $info) ? $info['severity'] : -1; if ($severity >= $min_severity) { drush_print_pipe($key . ': ' . $severity . "\n"); $severity = $severities[$severity]; $description = array_key_exists('value', $info) ? strip_tags($info['value']) : ''; if (array_key_exists('description', $info) && !empty($info['description'])) { if (!empty($description)) { $description .= "\n"; } $description .= strip_tags($info['description']); } $rows[] = array($info['title'], $severity, $description); } } if (count($rows) > 1) { drush_print_table($rows, TRUE, array(1 => 8)); } return $requirements; } /** * Command callback. Provides a birds-eye view of the current Drupal * installation. */ function drush_core_status() { $status_table = _core_site_status_table(drush_get_option('project',''), drush_get_option('full')); // If args are specified, filter out any entry that is not named // (in other words, only show lines named by one of the arg values) $args = func_get_args(); if (!empty($args)) { foreach ($status_table as $key => $value) { if (!_drush_core_is_named_in_array($key, $args)) { unset($status_table[$key]); } } } $return = $status_table; unset($status_table['%paths']); // Print either an ini-format list or a formatted ASCII table if (drush_get_option('pipe')) { if (count($status_table) == 1) { $first_value = array_shift($status_table); drush_print_pipe($first_value); } else { drush_print_pipe(_core_site_credential_list($status_table)); } } else { unset($status_table['Modules path']); unset($status_table['Themes path']); drush_print_table(drush_key_value_to_array_table($status_table)); } return $return; } // Command callback. Show all global options. Exposed via topic command. function drush_core_global_options() { drush_print(dt('These options are applicable to most drush commands.')); drush_print(); $fake = drush_global_options_command(FALSE); $global_option_rows = drush_format_help_section($fake, 'options'); drush_print_table($global_option_rows); drush_print(); drush_print("See also: `drush topic docs-strict-options`"); } function _drush_core_is_named_in_array($key, $the_array) { $is_named = FALSE; $simplified_key = str_replace(array(' ', '_', '-'), array('', '', ''), $key); foreach ($the_array as $name) { if (stristr($simplified_key, str_replace(array(' ', '_', '-'), array('', '', ''), $name))) { $is_named = TRUE; } } return $is_named; } /** * Callback for core-quick-drupal command. */ function drush_core_quick_drupal() { $requests = FALSE; $make_projects = array(); $args = func_get_args(); $name = drush_get_option('use-name'); drush_set_option('backend', TRUE); $makefile = drush_get_option('makefile'); if (drush_get_option('use-existing', FALSE)) { $root = drush_get_option('root', FALSE); if (!$root) { return drush_set_error('QUICK_DRUPAL_NO_ROOT_SPECIFIED', 'Must specify site with --root when using --use-existing.'); } if (empty($name)) { $name = basename($root); } $base = dirname($root); } else { if (!empty($args) && empty($name)) { $name = array_shift($args); } if (empty($name)) { $name = 'quick-drupal-' . gmdate('YmdHis', $_SERVER['REQUEST_TIME']); } $root = drush_get_option('root', FALSE); $core = drush_get_option('core', 'drupal'); $project_rename = $core; if ($root) { $base = dirname($root); $project_rename = basename($root); } else { $base = getcwd() . '/' . $name; $root = $base . '/' . $core; } if (!empty($makefile)) { // Invoke 'drush make $makefile'. $result = drush_invoke_process('@none', 'make', array($makefile, $root)); if ($result['error_status'] != 0) { return drush_set_error('DRUSH_QD_MAKE', 'Could not make; aborting.'); } $make_projects = array_diff(array_keys($result['object']['projects']), array('drupal')); } else { drush_mkdir($base); drush_set_option('destination', $base); drush_set_option('drupal-project-rename', $project_rename); if (drush_invoke('pm-download', array($core)) === FALSE) { return drush_set_error('QUICK_DRUPAL_CORE_DOWNLOAD_FAIL', 'Drupal core download/extract failed.'); } } } if (!drush_get_option('db-url', FALSE)) { drush_set_option('db-url', 'sqlite:' . $base . '/' . $name . '.sqlite'); } drush_set_option('root', $root); if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_ROOT)) { return drush_set_error('QUICK_DRUPAL_ROOT_LOCATE_FAIL', 'Unable to locate Drupal root directory.'); } if (!empty($args)) { $requests = pm_parse_arguments($args, FALSE); } if ($requests) { // Unset --destination, so that downloads go to the site directories. drush_unset_option('destination'); if (drush_invoke('pm-download', $requests) === FALSE) { return drush_set_error('QUICK_DRUPAL_PROJECT_DOWNLOAD_FAIL', 'Project download/extract failed.'); } } drush_invoke('site-install', array(drush_get_option('profile'))); // Log in with the admin user. // TODO: If site-install is given a sites-subdir other than 'default', // then it will bootstrap to DRUSH_BOOTSTRAP_DRUPAL_SITE get the installer // to recognize the desired site directory. This somehow interferes // with our desire to bootstrap to DRUSH_BOOTSTRAP_DRUPAL_LOGIN here. // We could do the last few steps in a new process iff uri is not 'default'. drush_set_option('user', '1'); if (!drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_LOGIN)) { return drush_set_error('QUICK_DRUPAL_INSTALL_FAIL', 'Drupal core install failed.'); } $enable = array_merge(pm_parse_arguments(drush_get_option('enable', $requests)), $make_projects); if (!empty($enable)) { if (drush_invoke('pm-enable', $enable) === FALSE) { return drush_set_error('QUICK_DRUPAL_PROJECT_ENABLE_FAIL', 'Project enable failed.'); } } drush_print(dt('Login URL: ') . drush_invoke('user-login')); if (!drush_get_option('no-server', FALSE)) { if ($server = drush_get_option('server', '/')) { drush_invoke('runserver', array($server)); } } } /** * Include options and engines for core-quick-drupal command, aggregated from * other command options that are available. We prefix option descriptons, * to make the long list more navigable. * * @param $items * The core commandfile command array, by reference. Used to include * site-install options and add options and engines for core-quick-drupal. */ function drush_core_quick_drupal_options(&$items) { $options = array( 'core' => 'Drupal core to download. Defaults to "drupal" (latest stable version).', 'use-existing' => 'Use an existing Drupal root, specified with --root. Overrides --core.', 'profile' => 'The install profile to use. Defaults to standard.', 'enable' => 'Specific extensions (modules or themes) to enable. By default, extensions with the same name as requested projects will be enabled automatically.', 'server' => 'Host IP address and port number to bind to and path to open in web browser (hyphen to clear a default path), all elements optional. See runserver examples for shorthand.', 'no-server' => 'Avoid starting runserver (and browser) for the created Drupal site.', 'browser' => 'Optional name of a browser to open site in. If omitted the OS default browser will be used. Set --no-browser to disable.', 'use-name' => array('hidden' => TRUE, 'description' => 'Overrides "name" argument.'), 'makefile' => array('description' => 'Makefile to use. Makefile must specify which version of Drupal core to build.', 'example-value' => 'mysite.make', 'value' => 'optional'), 'root' => array('description' => 'Path to Drupal root.', 'example-value' => '/path/to/root', 'value' => 'optional'), ); $pm = pm_drush_command(); foreach ($pm['pm-download']['options'] as $option => $description) { if (is_array($description)) { $description = $description['description']; } $options[$option] = 'Download option: ' . $description; } // Unset a few options that are not usable here, as we control them ourselves // or they are otherwise implied by the environment. unset($options['destination']); unset($options['drupal-project-rename']); unset($options['default-major']); unset($options['use-site-dir']); foreach ($items['site-install']['options'] as $option => $description) { if (is_array($description)) { $description = $description['description']; } $options[$option] = 'Site install option: ' . $description; } unset($options['sites-subdir']); $runserver = runserver_drush_command(); foreach ($runserver['runserver']['options'] as $option => $description) { $options[$option] = 'Runserver option: ' . $description; } unset($options['user']); $items['core-quick-drupal']['options'] = $options; $items['core-quick-drupal']['engines'] = $pm['pm-download']['engines']; } /** * Command callback. Runs "naked" php scripts * and drush "shebang" scripts ("#!/usr/bin/env drush"). * * @params * Command arguments, optional. First argument is site name, remaining * argument(s) are contrib modules to install. */ function drush_core_php_script() { $found = FALSE; $script = NULL; if ($args = func_get_args()) { $script = $args[0]; } if ($script == '-') { eval(stream_get_contents(STDIN)); } elseif (file_exists($script)) { $found = $script; } else { // Array of paths to search for scripts $searchpath['DIR'] = dirname(__FILE__); $searchpath['cwd'] = drush_cwd(); // Additional script paths, specified by 'script-path' option if ($script_path = drush_get_option('script-path', FALSE)) { foreach (explode(PATH_SEPARATOR, $script_path) as $path) { $searchpath[] = $path; } } drush_log(dt('Searching for scripts in ') . implode(',', $searchpath), 'debug'); if (!isset($script)) { // List all available scripts. $all = array(); foreach($searchpath as $key => $path) { $recurse = !(($key == 'cwd') || ($path == '/')); $all = array_merge( $all , array_keys(drush_scan_directory($path, '/\.php$/', array('.', '..', 'CVS'), NULL, $recurse)) ); } drush_print(implode("\n", $all)); } else { // Execute the specified script. foreach($searchpath as $path) { $script_filename = $path . '/' . $script; if (file_exists($script_filename . '.php')) { $script_filename .= '.php'; } if (file_exists($script_filename)) { $found = $script_filename; break; } $all[] = $script_filename; } if (!$found) { return drush_set_error('DRUSH_TARGET_NOT_FOUND', dt('Unable to find any of the following: @files', array('@files' => implode(', ', $all)))); } } } if ($found) { // Set the DRUSH_SHIFT_SKIP to two; this will cause // drush_shift to skip the next two arguments the next // time it is called. This allows scripts to get all // arguments, including the 'php-script' and script // pathname, via drush_get_arguments(), or it can process // just the arguments that are relevant using drush_shift(). drush_set_context('DRUSH_SHIFT_SKIP', 2); if (_drush_core_eval_shebang_script($found) === FALSE) { include($found); } } } function drush_core_php_eval($command) { return eval($command . ';'); } /* * Evaluate a script that begins with #!drush php-script */ function _drush_core_eval_shebang_script($script_filename) { $found = FALSE; $fp = fopen($script_filename, "r"); if ($fp !== FALSE) { $line = fgets($fp); if (_drush_is_drush_shebang_line($line)) { $first_script_line = ''; while ($line = fgets($fp)) { $line = trim($line); if ($line == ' $target))); } } /** * Called for `drush version` or `drush --version` */ function drush_core_version() { drush_print(dt("drush version !version", array('!version' => DRUSH_VERSION))); drush_print_pipe(DRUSH_VERSION); // Next check to see if there is a newer drush. if (!drush_get_context('DRUSH_PIPE') && drush_get_option('self-update', TRUE)) { drush_check_self_update(); } } function drush_core_self_update() { drush_check_self_update(); } function drush_core_find_project_path($target) { $path = drush_db_result(drush_db_select('system', array('filename'), 'name = :name', array(':name' => $target))); if ($path) { $path = dirname($path); return DRUPAL_ROOT . '/' . $path; } } /** * Command callback. Execute specified shell code. Often used by shell aliases * that start with !. */ function drush_core_execute() { // Get all of the args and options that appear after the command name. $args = drush_get_original_cli_args_and_options(); for ($x = 0; $x < sizeof($args); $x++) { // escape all args except for command separators. if (!in_array($args[$x], array('&&', '||', ';'))) { $args[$x] = drush_escapeshellarg($args[$x]); } } $cmd = implode(' ', $args); if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { $site = drush_sitealias_get_record($alias); if (!empty($site['remote-host'])) { // Remote, so execute an ssh command with a bash fragment at the end. $exec = drush_shell_proc_build($site, $cmd, TRUE); return drush_shell_proc_open($exec); } } // Must be a local command. return drush_shell_proc_open($cmd); } drush-5.10.0/commands/core/docs.drush.inc000066400000000000000000000263131222105546100202250ustar00rootroot00000000000000 TRUE to indicate the command is a topic (REQUIRED) // Begin the topic name with the name of the commandfile (just like // any other command). // $items['docs-readme'] = array( 'description' => 'README.md', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/README.md'), ); $items['docs-bashrc'] = array( 'description' => 'Bashrc customization examples for Drush.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/example.bashrc'), ); $items['docs-configuration'] = array( 'description' => 'Configuration overview with examples from example.drushrc.php.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/example.drushrc.php'), ); $items['docs-aliases'] = array( 'description' => 'Site aliases overview on creating your own aliases for commonly used Drupal sites with examples from example.aliases.drushrc.php.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/example.aliases.drushrc.php'), ); $items['docs-ini-files'] = array( 'description' => 'php.ini or drush.ini configuration to set PHP values for use with Drush.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/example.drush.ini'), ); $items['docs-bastion'] = array( 'description' => 'Bastion server configuration: remotely operate on a Drupal sites behind a firewall.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/bastion.html'), ); $items['docs-bootstrap'] = array( 'description' => 'Bootstrap explanation: how Drush starts up and prepared the Drupal envionment for use with the command.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/bootstrap.html'), ); $items['docs-cron'] = array( 'description' => 'Crontab instructions for running your Drupal cron tasks via `drush cron`.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/cron.html'), ); $items['docs-scripts'] = array( 'description' => 'Shell script overview on writing simple sequences of Drush statements.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/shellscripts.html'), ); $items['docs-shell-aliases'] = array( 'description' => 'Shell alias overview on creating your own aliases for commonly used Drush commands.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/shellaliases.html'), ); $items['docs-commands'] = array( 'description' => 'Drush command instructions on creating your own Drush commands.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/commands.html'), ); $items['docs-errorcodes'] = array( 'description' => 'Error code list containing all identifiers used with drush_set_error.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, ); $items['docs-api'] = array( 'description' => 'Drush API', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/drush.api.php'), ); $items['docs-context'] = array( 'description' => 'Contexts overview explaining how Drush manages command line options and configuration file settings.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/context.html'), ); $items['docs-examplescript'] = array( 'description' => 'Example Drush script.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/helloworld.script'), ); $items['docs-examplecommand'] = array( 'description' => 'Example Drush command file.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/sandwich.drush.inc'), ); $items['docs-example-sync-extension'] = array( 'description' => 'Example Drush commandfile that extends sql-sync to enable development modules in the post-sync hook.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/sync_enable.drush.inc'), ); $items['docs-example-sync-via-http'] = array( 'description' => 'Example Drush commandfile that extends sql-sync to allow transfer of the sql dump file via http rather than ssh and rsync.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/sync_via_http.drush.inc'), ); $items['docs-policy'] = array( 'description' => 'Example policy file.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/policy.drush.inc'), ); $items['docs-strict-options'] = array( 'description' => 'Strict option handling, and how commands that use it differ from regular Drush commands.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/strict-options.html'), ); return $items; } /** * docs-error-codes command. Print a list of all error codes * that can be found. */ function drush_docs_errorcodes() { $header = << $command) { $files = array_merge($files, drush_command_get_includes($command_name)); } // We will also search through all of the .inc files in the // drush includes directory $drush_include_files = drush_scan_directory(DRUSH_BASE_PATH . '/includes', '/.*\.inc$/', array('.', '..', 'CVS'), 0, FALSE); foreach ($drush_include_files as $filename => $info) { $files[$filename] = 'include'; } // Extract error messages from all command files $error_list = array(); foreach ($files as $file => $commandfile) { _drush_docs_find_set_error_calls($error_list, $file, $commandfile); } // Order error messages alphabetically by key ksort($error_list); // Convert to a table $data = array(); foreach ($error_list as $error_code => $error_messages) { $data[] = array($error_code, '-', implode("\n", $error_messages)); } $tmpfile = drush_tempnam('drush-errorcodes.'); file_put_contents($tmpfile, $header); drush_print_table($data, FALSE, array(0 => 35), $tmpfile); drush_print_file($tmpfile); } /** * Search through a php source file looking for calls to * the function drush_set_error. If found, and if the * first parameter is an uppercase alphanumeric identifier, * then record the error code and the error message in our table. */ function _drush_docs_find_set_error_calls(&$error_list, $filename, $shortname) { $lines = file($filename); foreach ($lines as $line) { $matches = array(); // Find the error code after the drush_set_error call. The error code // should consist of uppercase letters and underscores only (numbers thrown in just in case) $match_result = preg_match("/.*drush_set_error[^'\"]['\"]([A-Z0-9_]*)['\"][^,]*,[^'\"]*(['\"])/", $line, $matches); if ($match_result) { $error_code = $matches[1]; $quote_char = $matches[2]; $error_message = ""; $message_start = strlen($matches[0]) - 1; // Regex adapted from http://stackoverflow.com/questions/1824325/regex-expression-for-escaped-quoted-string-wont-work-in-phps-preg-match-allif ($quote_char == '"') { if ($quote_char == '"') { $regex = '/"((?:[^\\\]*?(?:\\\")?)*?)"/'; } else { $regex = "/'((?:[^\\\]*?(?:\\\')?)*?)'/"; } $match_result = preg_match($regex, $line, $matches, 0, $message_start); if ($match_result) { $error_message = $matches[1]; } $error_list[$error_code] = array_key_exists($error_code, $error_list) ? array_merge($error_list[$error_code], array($error_message)) : array($error_message); } } } drush-5.10.0/commands/core/drupal/000077500000000000000000000000001222105546100167405ustar00rootroot00000000000000drush-5.10.0/commands/core/drupal/batch.inc000066400000000000000000000201071222105546100205140ustar00rootroot00000000000000 0, ); $batch += $process_info; // The batch is now completely built. Allow other modules to make changes // to the batch so that it is easier to reuse batch processes in other // enviroments. drupal_alter('batch', $batch); // Assign an arbitrary id: don't rely on a serial column in the 'batch' // table, since non-progressive batches skip database storage completely. $batch['id'] = db_next_id(); $args[] = $batch['id']; $batch['progressive'] = TRUE; // Move operations to a job queue. Non-progressive batches will use a // memory-based queue. foreach ($batch['sets'] as $key => $batch_set) { _batch_populate_queue($batch, $key); } // Store the batch. db_insert('batch') ->fields(array( 'bid' => $batch['id'], 'timestamp' => REQUEST_TIME, 'token' => drupal_get_token($batch['id']), 'batch' => serialize($batch), )) ->execute(); $finished = FALSE; global $user; while (!$finished) { if ($user->uid) { $data = drush_invoke_process('@self', $command, $args, array($batch['id'], '-u', $user->uid)); } else { $data = drush_invoke_process('@self', $command, $args, array($batch['id'])); } $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); } } } /** * Initialize the batch command and call the worker function. * * Loads the batch record from the database and sets up the requirements * for the worker, such as registering the shutdown function. * * @param id * The batch id of the batch being processed. */ function _drush_batch_command($id) { $batch =& batch_get(); $data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array( ':bid' => $id))->fetchField(); if ($data) { $batch = unserialize($data); } else { return FALSE; } if (!isset($batch['running'])) { $batch['running'] = TRUE; } // Register database update for end of processing. register_shutdown_function('_drush_batch_shutdown'); if (_drush_batch_worker()) { _drush_batch_finished(); } } /** * Process batch operations * * Using the current $batch process each of the operations until the batch * has been completed or half of the available memory for the process has been * reached. */ function _drush_batch_worker() { $batch =& batch_get(); $current_set =& _batch_current_set(); $set_changed = TRUE; timer_start('batch_processing'); if (empty($current_set['start'])) { $current_set['start'] = microtime(TRUE); } $queue = _batch_queue($current_set); while (!$current_set['success']) { // If this is the first time we iterate this batch set in the current // request, we check if it requires an additional file for functions // definitions. if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { include_once DRUPAL_ROOT . '/' . $current_set['file']; } $task_message = ''; // Assume a single pass operation and set the completion level to 1 by // default. $finished = 1; if ($item = $queue->claimItem()) { list($function, $args) = $item->data; // Build the 'context' array and execute the function call. $batch_context = array( 'sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message, ); // Magic wrap to catch changes to 'message' key. $batch_context = new DrushBatchContext($batch_context); call_user_func_array($function, array_merge($args, array(&$batch_context))); $finished = $batch_context['finished']; if ($finished >= 1) { // Make sure this step is not counted twice when computing $current. $finished = 0; // Remove the processed operation and clear the sandbox. $queue->deleteItem($item); $current_set['count']--; $current_set['sandbox'] = array(); } } // When all operations in the current batch set are completed, browse // through the remaining sets, marking them 'successfully processed' // along the way, until we find a set that contains operations. // _batch_next_set() executes form submit handlers stored in 'control' // sets (see form_execute_handlers()), which can in turn add new sets to // the batch. $set_changed = FALSE; $old_set = $current_set; while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) { $current_set = &_batch_current_set(); $current_set['start'] = microtime(TRUE); $set_changed = TRUE; } // At this point, either $current_set contains operations that need to be // processed or all sets have been completed. $queue = _batch_queue($current_set); // If we are in progressive mode, break processing after 1 second. if ((memory_get_usage() * 2) >= drush_memory_limit()) { drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), "batch"); // Record elapsed wall clock time. $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2); break; } } // Reporting 100% progress will cause the whole batch to be considered // processed. If processing was paused right after moving to a new set, // we have to use the info from the new (unprocessed) set. if ($set_changed && isset($current_set['queue'])) { // Processing will continue with a fresh batch set. $remaining = $current_set['count']; $total = $current_set['total']; $progress_message = $current_set['init_message']; $task_message = ''; } else { // Processing will continue with the current batch set. $remaining = $old_set['count']; $total = $old_set['total']; $progress_message = $old_set['progress_message']; } $current = $total - $remaining + $finished; $percentage = _batch_api_percentage($total, $current); return ($percentage == 100); } /** * End the batch processing: * Call the 'finished' callbacks to allow custom handling of results, * and resolve page redirection. */ function _drush_batch_finished() { $batch = &batch_get(); // Execute the 'finished' callbacks for each batch set, if defined. foreach ($batch['sets'] as $batch_set) { if (isset($batch_set['finished'])) { // Check if the set requires an additional file for function definitions. if (isset($batch_set['file']) && is_file($batch_set['file'])) { include_once DRUPAL_ROOT . '/' . $batch_set['file']; } if (function_exists($batch_set['finished'])) { $queue = _batch_queue($batch_set); $operations = $queue->getAllItems(); $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000)); } } } // Clean up the batch table and unset the static $batch variable. db_delete('batch') ->condition('bid', $batch['id']) ->execute(); foreach ($batch['sets'] as $batch_set) { if ($queue = _batch_queue($batch_set)) { $queue->deleteQueue(); } } $_batch = $batch; $batch = NULL; drush_set_option('drush_batch_process_finished', TRUE); } /** * Shutdown function: store the batch data for next request, * or clear the table if the batch is finished. */ function _drush_batch_shutdown() { if ($batch = batch_get()) { db_update('batch') ->fields(array('batch' => serialize($batch))) ->condition('bid', $batch['id']) ->execute(); } } drush-5.10.0/commands/core/drupal/batch_6.inc000066400000000000000000000150441222105546100207450ustar00rootroot00000000000000 0, ); $batch += $process_info; // Initiate db storage in order to get a batch id. We have to provide // at least an empty string for the (not null) 'token' column. db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time()); $batch['id'] = db_last_insert_id('batch', 'bid'); $args[] = $batch['id']; // Actually store the batch data and the token generated form the batch id. db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']); $finished = FALSE; while (!$finished) { $data = drush_invoke_process('@self', $command, $args, $options); $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); } } } /** * Initialize the batch command and call the worker function. * * Loads the batch record from the database and sets up the requirements * for the worker, such as registering the shutdown function. * * @param id * The batch id of the batch being processed. */ function _drush_batch_command($id) { $batch =& batch_get(); // Retrieve the current state of batch from db. if ($data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $id))) { $batch = unserialize($data); } else { return FALSE; } if (!isset($batch['running'])) { $batch['running'] = TRUE; } // Register database update for end of processing. register_shutdown_function('_drush_batch_shutdown'); if (_drush_batch_worker()) { _drush_batch_finished(); } } /** * Process batch operations * * Using the current $batch process each of the operations until the batch * has been completed or half of the available memory for the process has been * reached. */ function _drush_batch_worker() { $batch =& batch_get(); $current_set =& _batch_current_set(); $set_changed = TRUE; timer_start('batch_processing'); while (!$current_set['success']) { // If this is the first time we iterate this batch set in the current // request, we check if it requires an additional file for functions // definitions. if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { include_once($current_set['file']); } $finished = 1; $task_message = ''; if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) { // Build the 'context' array, execute the function call, // and retrieve the user message. $batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message); // Magic wrap to catch changes to 'message' key. $batch_context = new DrushBatchContext($batch_context); // Process the current operation. call_user_func_array($function, array_merge($args, array(&$batch_context))); $finished = $batch_context['finished']; } if ($finished >= 1) { // Make sure this step isn't counted double when computing $current. $finished = 0; // Remove the operation and clear the sandbox. array_shift($current_set['operations']); $current_set['sandbox'] = array(); } // If the batch set is completed, browse through the remaining sets, // executing 'control sets' (stored form submit handlers) along the way - // this might in turn insert new batch sets. // Stop when we find a set that actually has operations. $set_changed = FALSE; $old_set = $current_set; while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) { $current_set =& _batch_current_set(); $set_changed = TRUE; } // At this point, either $current_set is a 'real' batch set (has operations), // or all sets have been completed. // TODO - replace with memory check! // If we're in progressive mode, stop after 1 second. if ((memory_get_usage() * 2) >= drush_memory_limit()) { drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), "batch"); break; } } // Gather progress information. // Reporting 100% progress will cause the whole batch to be considered // processed. If processing was paused right after moving to a new set, // we have to use the info from the new (unprocessed) one. if ($set_changed && isset($current_set['operations'])) { // Processing will continue with a fresh batch set. $remaining = count($current_set['operations']); $total = $current_set['total']; $task_message = ''; } else { $remaining = count($old_set['operations']); $total = $old_set['total']; } $current = $total - $remaining + $finished; $percentage = $total ? floor($current / $total * 100) : 100; return ($percentage == 100); } /** * End the batch processing: * Call the 'finished' callbacks to allow custom handling of results, * and resolve page redirection. */ function _drush_batch_finished() { $batch =& batch_get(); // Execute the 'finished' callbacks for each batch set. foreach ($batch['sets'] as $key => $batch_set) { if (isset($batch_set['finished'])) { // Check if the set requires an additional file for functions definitions. if (isset($batch_set['file']) && is_file($batch_set['file'])) { include_once($batch_set['file']); } if (function_exists($batch_set['finished'])) { $batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']); } } } // Cleanup the batch table and unset the global $batch variable. db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']); $_batch = $batch; $batch = NULL; drush_set_option('drush_batch_process_finished', TRUE); } /** * Shutdown function: store the batch data for next request, * or clear the table if the batch is finished. */ function _drush_batch_shutdown() { if ($batch = batch_get()) { db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']); } } drush-5.10.0/commands/core/drupal/environment.inc000066400000000000000000000142151222105546100220020ustar00rootroot00000000000000 $module) { if (isset($module->info['hidden'])) { unset($modules[$key]); } } } return $modules; } /** * Returns drupal required modules, including modules declared as required dynamically. */ function _drush_drupal_required_modules($module_info) { $required = drupal_required_modules(); foreach ($module_info as $name => $module) { if (isset($module->info['required']) && $module->info['required']) { $required[] = $name; } } return array_unique($required); } /** * Return dependencies and its status for modules. * * @param $modules * Array of module names * @param $module_info * Drupal 'files' array for modules as returned by drush_get_modules(). * @return * Array with dependencies and status for $modules */ function drush_check_module_dependencies($modules, $module_info) { $status = array(); foreach ($modules as $key => $module) { $dependencies = array_reverse($module_info[$module]->requires); $unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info)); if (!empty($unmet_dependencies)) { $status[$key]['error'] = array( 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) ); } else { // check for version incompatibility foreach ($dependencies as $dependency_name => $v) { $current_version = $module_info[$dependency_name]->info['version']; $current_version = str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $current_version); $incompatibility = drupal_check_incompatibility($v, $current_version); if (!is_null($incompatibility)) { $status[$key]['error'] = array( 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH', 'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version)) ); } } } $status[$key]['unmet-dependencies'] = $unmet_dependencies; $status[$key]['dependencies'] = array_keys($dependencies); } return $status; } /** * Return dependents of modules. * * @param $modules * Array of module names * @param $module_info * Drupal 'files' array for modules as returned by drush_get_modules(). * @return * Array with dependents for each one of $modules */ function drush_module_dependents($modules, $module_info) { $dependents = array(); foreach ($modules as $module) { $dependents = array_merge($dependents, drupal_map_assoc(array_keys($module_info[$module]->required_by))); } return array_unique($dependents); } /** * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled. * * @param $modules * Array of module names */ function drush_module_enable($modules) { // The list of modules already have all the dependencies, but they might not // be in the correct order. Still pass $enable_dependencies = TRUE so that // Drupal will enable the modules in the correct order. module_enable($modules); // Flush all caches. drupal_flush_all_caches(); } /** * Disable a list of modules. It is assumed the list contains all dependents not already disabled. * * @param $modules * Array of module names */ function drush_module_disable($modules) { // The list of modules already have all the dependencies, but they might not // be in the correct order. Still pass $enable_dependencies = TRUE so that // Drupal will enable the modules in the correct order. module_disable($modules); // Flush all caches. drupal_flush_all_caches(); } /** * Uninstall a list of modules. * * @param $modules * Array of module names */ function drush_module_uninstall($modules) { require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; drupal_uninstall_modules($modules); } /** * Get complete information for all available themes. * * @param $include_hidden * Boolean to indicate whether hidden themes should be excluded or not. * @return * An array containing theme info for all available themes. */ function drush_get_themes($include_hidden = TRUE) { $themes = system_rebuild_theme_data(); if (!$include_hidden) { foreach ($themes as $key => $theme) { if (isset($theme->info['hidden'])) { unset($themes[$key]); } } } return $themes; } /** * Enable a list of themes. * * @param $themes * Array of theme names. */ function drush_theme_enable($themes) { theme_enable($themes); } /** * Disable a list of themes. * * @param $themes * Array of theme names. */ function drush_theme_disable($themes) { theme_disable($themes); } /** * Helper function to obtain the severity levels based on Drupal version. * * This is a copy of watchdog_severity_levels() without t(). * * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. * * @return * Array of watchdog severity levels. */ function core_watchdog_severity_levels() { return array( WATCHDOG_EMERGENCY=> 'emergency', WATCHDOG_ALERT => 'alert', WATCHDOG_CRITICAL => 'critical', WATCHDOG_ERROR => 'error', WATCHDOG_WARNING => 'warning', WATCHDOG_NOTICE => 'notice', WATCHDOG_INFO => 'info', WATCHDOG_DEBUG => 'debug', ); } drush-5.10.0/commands/core/drupal/environment_6.inc000066400000000000000000000153601222105546100222310ustar00rootroot00000000000000 $module) { if (!isset($module->type)) { $module->type = 'module'; } if ((!$include_hidden) && isset($module->info['hidden']) && ($module->info['hidden'])) { unset($modules[$key]); } } return $modules; } /** * Returns drupal required modules, including their dependencies. * * A module may alter other module's .info to set a dependency on it. * See for example http://drupal.org/project/phpass */ function _drush_drupal_required_modules($module_info) { $required = drupal_required_modules(); foreach ($required as $module) { $required = array_merge($required, $module_info[$module]->info['dependencies']); } return $required; } /** * Return dependencies and its status for modules. * * @param $modules * Array of module names * @param $module_info * Drupal 'files' array for modules as returned by drush_get_modules(). * @return * Array with dependencies and status for $modules */ function drush_check_module_dependencies($modules, $module_info) { $status = array(); foreach ($modules as $key => $module) { $dependencies = array_reverse($module_info[$module]->info['dependencies']); $unmet_dependencies = array_diff($dependencies, array_keys($module_info)); if (!empty($unmet_dependencies)) { $status[$key]['error'] = array( 'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND', 'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies))) ); } $status[$key]['unmet-dependencies'] = $unmet_dependencies; $status[$key]['dependencies'] = $dependencies; } return $status; } /** * Return dependents of modules. * * @param $modules * Array of module names * @param $module_info * Drupal 'files' array for modules as returned by drush_get_modules(). * @return * Array with dependents for each one of $modules */ function drush_module_dependents($modules, $module_info) { $dependents = array(); foreach ($modules as $module) { $dependents = array_merge($dependents, $module_info[$module]->info['dependents']); } return array_unique($dependents); } /** * Enable a list of modules. It is assumed the list contains all the dependencies not already enabled. * * @param $modules * Array of module names */ function drush_module_enable($modules) { // Try to install modules previous to enabling. foreach ($modules as $module) { _drupal_install_module($module); } module_enable($modules); drush_system_modules_form_submit(); } /** * Disable a list of modules. It is assumed the list contains all dependents not already disabled. * * @param $modules * Array of module names */ function drush_module_disable($modules) { module_disable($modules, FALSE); drush_system_modules_form_submit(); } /** * Uninstall a list of modules. * * @param $modules * Array of module names */ function drush_module_uninstall($modules) { require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; foreach ($modules as $module) { drupal_uninstall_module($module); } } /** * Submit the system modules form. * * The modules should already be fully enabled/disabled before calling this * function. Calling this function just makes sure any activities triggered by * the form submit (such as admin_role) are completed. */ function drush_system_modules_form_submit() { $active_modules = array(); foreach (drush_get_modules(FALSE) as $key => $module) { if ($module->status == 1) { $active_modules[$key] = $key; } } module_load_include('inc', 'system', 'system.admin'); $form_state = array('values' => array('status' => $active_modules)); drupal_execute('system_modules', $form_state); } /** * Get complete information for all available themes. * * We need to set the type for those themes that are not already in the system table. * * @param $include_hidden * Boolean to indicate whether hidden themes should be excluded or not. * @return * An array containing theme info for all available themes. */ function drush_get_themes($include_hidden = TRUE) { $themes = system_theme_data(); foreach ($themes as $key => $theme) { if (!isset($theme->type)) { $theme->type = 'theme'; } if ((!$include_hidden) && isset($theme->info['hidden']) && ($theme->info['hidden'])) { unset($themes[$key]); } } return $themes; } /** * Enable a list of themes. * * This function is based on system_themes_form_submit(). * * @see system_themes_form_submit() * @param $themes * Array of theme names. */ function drush_theme_enable($themes) { drupal_clear_css_cache(); foreach ($themes as $theme) { system_initialize_theme_blocks($theme); } db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes); list_themes(TRUE); menu_rebuild(); module_invoke('locale', 'system_update', $themes); } /** * Disable a list of themes. * * This function is based on system_themes_form_submit(). * * @see system_themes_form_submit() * @param $themes * Array of theme names. */ function drush_theme_disable($themes) { drupal_clear_css_cache(); db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes); list_themes(TRUE); menu_rebuild(); drupal_rebuild_theme_registry(); module_invoke('locale', 'system_update', $themes); } /** * Helper function to obtain the severity levels based on Drupal version. * * This is a copy of watchdog_severity_levels() without t(). * * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. * * @return * Array of watchdog severity levels. */ function core_watchdog_severity_levels() { return array( WATCHDOG_EMERG => 'emergency', WATCHDOG_ALERT => 'alert', WATCHDOG_CRITICAL => 'critical', WATCHDOG_ERROR => 'error', WATCHDOG_WARNING => 'warning', WATCHDOG_NOTICE => 'notice', WATCHDOG_INFO => 'info', WATCHDOG_DEBUG => 'debug', ); } drush-5.10.0/commands/core/drupal/site_install.inc000066400000000000000000000036261222105546100221340ustar00rootroot00000000000000 array( 'profile' => $profile, 'locale' => drush_get_option('locale', 'en'), ), 'forms' => array( 'install_settings_form' => array( 'driver' => $db_spec['driver'], $db_spec['driver'] => $db_spec, 'op' => dt('Save and continue'), ), 'install_configure_form' => array( 'site_name' => drush_get_option('site-name', 'Site-Install'), 'site_mail' => drush_get_option('site-mail', 'admin@example.com'), 'account' => array( 'name' => $account_name, 'mail' => drush_get_option('account-mail', 'admin@example.com'), 'pass' => array( 'pass1' => $account_pass, 'pass2' => $account_pass, ), ), 'update_status_module' => array( 1 => TRUE, 2 => TRUE, ), 'clean_url' => drush_get_option('clean-url', TRUE), 'op' => dt('Save and continue'), ), ), ); // Merge in the additional options. foreach ($additional_form_options as $key => $value) { $current = &$settings['forms']; foreach (explode('.', $key) as $param) { $current = &$current[$param]; } $current = $value; } drush_log(dt('Starting Drupal installation. This takes a few seconds ...'), 'ok'); install_drupal($settings); drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), 'ok'); } drush-5.10.0/commands/core/drupal/site_install_6.inc000066400000000000000000000126071222105546100223600ustar00rootroot00000000000000&1 // redirect added to the command in drush_shell_exec(). We will actually take out // all but fatal errors. See http://drupal.org/node/985716 for more information. $phpcode = 'error_reporting(E_ERROR);' . _drush_site_install6_cookies($profile). ' include("'. $drupal_root .'/install.php");'; drush_shell_exec('php -r %s', $phpcode); $cli_output = drush_shell_exec_output(); $cli_cookie = end($cli_output); // We need to bootstrap the database to be able to check the progress of the // install batch process since we're not duplicating the install process using // drush_batch functions, but calling the process directly. drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); $status = _drush_site_install6_stage($profile, $cli_cookie, "start"); if ($status === FALSE) { return FALSE; } $status = _drush_site_install6_stage($profile, $cli_cookie, "do_nojs"); if ($status === FALSE) { return FALSE; } $status = _drush_site_install6_stage($profile, $cli_cookie, "finished"); if ($status === FALSE) { return FALSE; } $account_pass = drush_get_option('account-pass', drush_generate_password()); $account_name = drush_get_option('account-name', 'admin'); $phpcode = _drush_site_install6_cookies($profile, $cli_cookie); $post = array ( "site_name" => drush_get_option('site-name', 'Site-Install'), "site_mail" => drush_get_option('site-mail', 'admin@example.com'), "account" => array ( "name" => $account_name, "mail" => drush_get_option('account-mail', 'admin@example.com'), "pass" => array ( "pass1" => $account_pass, "pass2" => $account_pass, ) ), "date_default_timezone" => "0", "clean_url" => drush_get_option('clean-url', TRUE), "form_id" => "install_configure_form", "update_status_module" => array("1" => "1"), ); // Merge in the additional options. foreach ($additional_form_options as $key => $value) { $current = &$post; foreach (explode('.', $key) as $param) { $current = &$current[$param]; } $current = $value; } $phpcode .= ' $_POST = ' . var_export($post, true) . '; include("'. $drupal_root .'/install.php");'; drush_shell_exec('php -r %s', $phpcode); drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), 'ok'); } /** * Submit a given op to install.php; if a meta "Refresh" tag * is returned in the result, then submit that op as well. */ function _drush_site_install6_stage($profile, $cli_cookie, $initial_op) { $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); // Remember the install task at the start of the stage $install_task = _drush_site_install6_install_task(); $op = $initial_op; while (!empty($op)) { $phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="' . $op . '"; include("'. $drupal_root .'/install.php");'; drush_shell_exec('php -r %s', $phpcode); $output = implode("\n", drush_shell_exec_output()); // Check for a "Refresh" back to the do_nojs op; e.g.: // // If this pattern is NOT found, then go on to the "finished" step. $matches = array(); $match_result = preg_match('/http-equiv="Refresh".*op=([a-zA-Z0-9_]*)/', $output, $matches); if ($match_result) { $op = $matches[1]; } else { $op = ''; } } if (($install_task == _drush_site_install6_install_task()) && ($initial_op != "finished")) { return drush_set_error('DRUSH_SITE_INSTALL_FAILED', dt("The site install task '!task' failed.", array('!task' => $install_task))); } return TRUE; } /** * Utility function to grab/set current "cli cookie". */ function _drush_site_install6_cookies($profile, $cookie = NULL) { $drupal_base_url = parse_url(drush_get_option('uri', 'http://default')); $output = '$_GET=array("profile"=>"' . $profile . '", "locale"=>"' . drush_get_option('locale', 'en') . '", "id"=>"1"); $_REQUEST=&$_GET;'; $output .= 'define("DRUSH_SITE_INSTALL6", TRUE);$_SERVER["SERVER_SOFTWARE"] = NULL;'; $output .= '$_SERVER["SCRIPT_NAME"] = "/install.php";'; $output .= '$_SERVER["HTTP_HOST"] = "'.$drupal_base_url['host'].'";'; $output .= '$_SERVER["REMOTE_ADDR"] = "127.0.0.1";'; if ($cookie) { $output .= sprintf('$_COOKIE=unserialize("%s");', str_replace('"', '\"', $cookie)); } else { $output .= 'function _cli_cookie_print(){print(serialize(array(session_name()=>session_id())));} register_shutdown_function("_cli_cookie_print");'; } return $output; } /** * Utility function to check the install_task. We are * not bootstrapped to a high enough level to use variable_get. */ function _drush_site_install6_install_task() { if ($data = db_result(db_query("SELECT value FROM {variable} WHERE name = 'install_task'",1))) { $result = unserialize($data); } return $result; } drush-5.10.0/commands/core/drupal/update.inc000066400000000000000000000242251222105546100207220ustar00rootroot00000000000000 FALSE, 'query' => 'What went wrong'); * The schema version will not be updated in this case, and all the * aborted updates will continue to appear on update.php as updates that * have not yet been run. * * @param $module * The module whose update will be run. * @param $number * The update number to run. * @param $context * The batch context array */ function drush_update_do_one($module, $number, $dependency_map, &$context) { $function = $module . '_update_' . $number; // If this update was aborted in a previous step, or has a dependency that // was aborted in a previous step, go no further. if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { return; } $context['log'] = FALSE; $ret = array(); if (function_exists($function)) { try { if ($context['log']) { Database::startLog($function); } drush_log("Executing " . $function); $ret['results']['query'] = $function($context['sandbox']); $ret['results']['success'] = TRUE; } // @TODO We may want to do different error handling for different exception // types, but for now we'll just print the message. catch (Exception $e) { $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); drush_set_error('DRUPAL_EXCEPTION', $e->getMessage()); } if ($context['log']) { $ret['queries'] = Database::getLog($function); } } if (isset($context['sandbox']['#finished'])) { $context['finished'] = $context['sandbox']['#finished']; unset($context['sandbox']['#finished']); } if (!isset($context['results'][$module])) { $context['results'][$module] = array(); } if (!isset($context['results'][$module][$number])) { $context['results'][$module][$number] = array(); } $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); if (!empty($ret['#abort'])) { // Record this function in the list of updates that were aborted. $context['results']['#abort'][] = $function; } // Record the schema update if it was completed successfully. if ($context['finished'] == 1 && empty($ret['#abort'])) { drupal_set_installed_schema_version($module, $number); } $context['message'] = 'Performing ' . $function; } /** * Check update requirements and report any errors. */ function update_check_requirements() { $warnings = FALSE; // Check the system module and update.php requirements only. $requirements = system_requirements('update'); $requirements += update_extra_requirements(); // If there are issues, report them. foreach ($requirements as $requirement) { if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) { $message = isset($requirement['description']) ? $requirement['description'] : ''; if (isset($requirement['value']) && $requirement['value']) { $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')'; } $warnings = TRUE; drupal_set_message($message, 'warning'); } } return $warnings; } function update_main_prepare() { // Some unavoidable errors happen because the database is not yet up-to-date. // Our custom error handler is not yet installed, so we just suppress them. drush_errors_off(); // We prepare a minimal bootstrap for the update requirements check to avoid // reaching the PHP memory limit. $core = DRUSH_DRUPAL_CORE; require_once $core . '/includes/bootstrap.inc'; require_once $core . '/includes/common.inc'; require_once $core . '/includes/file.inc'; include_once $core . '/includes/unicode.inc'; update_prepare_d8_bootstrap(); drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); require_once $core . '/includes/install.inc'; require_once $core . '/modules/system/system.install'; // Load module basics. include_once $core . '/includes/module.inc'; $module_list['system']['filename'] = 'modules/system/system.module'; module_list(TRUE, FALSE, FALSE, $module_list); drupal_load('module', 'system'); // Reset the module_implements() cache so that any new hook implementations // in updated code are picked up. module_implements('', FALSE, TRUE); // Set up $language, since the installer components require it. drupal_language_initialize(); // Set up theme system for the maintenance page. drupal_maintenance_theme(); // Check the update requirements for Drupal. update_check_requirements(); // update_fix_d7_requirements() needs to run before bootstrapping beyond path. // So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc. drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE); update_fix_d8_requirements(); // Now proceed with a full bootstrap. drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); drupal_maintenance_theme(); drush_errors_on(); include_once $core . '/includes/batch.inc'; drupal_load_updates(); update_fix_compatibility(); // Change query-strings on css/js files to enforce reload for all users. _drupal_flush_css_js(); // Flush the cache of all data for the update status module. if (db_table_exists('cache_update')) { cache_clear_all('*', 'cache_update', TRUE); } module_implements_reset(); } function update_main() { update_main_prepare(); $pending = update_get_update_list(); $start = array(); // Ensure system module's updates run first. $start['system'] = array(); // Print a list of pending updates for this module and get confirmation. if (sizeof($pending)) { drush_print(dt('The following updates are pending:')); drush_print(); foreach ($pending as $module => $updates) { if (isset($updates['start'])) { drush_print($module . ' module : '); if (isset($updates['start'])) { $start[$module] = $updates['start']; foreach ($updates['pending'] as $update) { drush_print($update, 2); } } drush_print(); } } if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { return drush_user_abort(); } drush_update_batch($start); } else { drush_log(dt("No database updates required"), 'success'); } } function _update_batch_command($id) { update_main_prepare(); drush_batch_command($id); } /** * Start the database update batch process. * * @param $start * An array of all the modules and which update to start at. * @param $redirect * Path to redirect to when the batch has finished processing. * @param $url * URL of the batch processing page (should only be used for separate * scripts like update.php). * @param $batch * Optional parameters to pass into the batch API. * @param $redirect_callback * (optional) Specify a function to be called to redirect to the progressive * processing page. */ function drush_update_batch($start) { // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $updates = update_resolve_dependencies($start); // Store the dependencies for each update function in an array which the // batch API can pass in to the batch operation each time it is called. (We // do not store the entire update dependency array here because it is // potentially very large.) $dependency_map = array(); foreach ($updates as $function => $update) { $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); } $operations = array(); foreach ($updates as $update) { if ($update['allowed']) { // Set the installed version of each module so updates will start at the // correct place. (The updates are already sorted, so we can simply base // this on the first one we come across in the above foreach loop.) if (isset($start[$update['module']])) { drupal_set_installed_schema_version($update['module'], $update['number'] - 1); unset($start[$update['module']]); } // Add this update function to the batch. $function = $update['module'] . '_update_' . $update['number']; $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); } } $batch['operations'] = $operations; $batch += array( 'title' => 'Updating', 'init_message' => 'Starting updates', 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', 'finished' => 'drush_update_finished', 'file' => 'includes/update.inc', ); batch_set($batch); drush_backend_batch_process('updatedb-batch-process'); } function drush_update_finished($success, $results, $operations) { // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback. } drush-5.10.0/commands/core/drupal/update_6.inc000066400000000000000000000452121222105546100211460ustar00rootroot00000000000000 "''" * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL * version will set values of the added column in old rows to the * DEFAULT value. * * @param $ret * Array to which results will be added. * @param $table * Name of the table, without {} * @param $column * Name of the column * @param $type * Type of column * @param $attributes * Additional optional attributes. Recognized attributes: * not null => TRUE|FALSE * default => NULL|FALSE|value (the value must be enclosed in '' marks) * @return * nothing, but modifies $ret parameter. */ function db_add_column(&$ret, $table, $column, $type, $attributes = array()) { if (array_key_exists('not null', $attributes) and $attributes['not null']) { $not_null = 'NOT NULL'; } if (array_key_exists('default', $attributes)) { if (is_null($attributes['default'])) { $default_val = 'NULL'; $default = 'default NULL'; } elseif ($attributes['default'] === FALSE) { $default = ''; } else { $default_val = "$attributes[default]"; $default = "default $attributes[default]"; } } $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column $type"); if (!empty($default)) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET $default"); } if (!empty($not_null)) { if (!empty($default)) { $ret[] = update_sql("UPDATE {". $table ."} SET $column = $default_val"); } $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column SET NOT NULL"); } } /** * Change a column definition using syntax appropriate for PostgreSQL. * Save result of SQL commands in $ret array. * * Remember that changing a column definition involves adding a new column * and dropping an old one. This means that any indices, primary keys and * sequences from serial-type columns are dropped and might need to be * recreated. * * @param $ret * Array to which results will be added. * @param $table * Name of the table, without {} * @param $column * Name of the column to change * @param $column_new * New name for the column (set to the same as $column if you don't want to change the name) * @param $type * Type of column * @param $attributes * Additional optional attributes. Recognized attributes: * not null => TRUE|FALSE * default => NULL|FALSE|value (with or without '', it won't be added) * @return * nothing, but modifies $ret parameter. */ function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) { if (array_key_exists('not null', $attributes) and $attributes['not null']) { $not_null = 'NOT NULL'; } if (array_key_exists('default', $attributes)) { if (is_null($attributes['default'])) { $default_val = 'NULL'; $default = 'default NULL'; } elseif ($attributes['default'] === FALSE) { $default = ''; } else { $default_val = "$attributes[default]"; $default = "default $attributes[default]"; } } $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $column TO ". $column ."_old"); $ret[] = update_sql("ALTER TABLE {". $table ."} ADD $column_new $type"); $ret[] = update_sql("UPDATE {". $table ."} SET $column_new = ". $column ."_old"); if ($default) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET $default"); } if ($not_null) { $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $column_new SET NOT NULL"); } $ret[] = update_sql("ALTER TABLE {". $table ."} DROP ". $column ."_old"); } /** * Disable anything in the {system} table that is not compatible with the * current version of Drupal core. */ function update_fix_compatibility() { $ret = array(); $incompatible = array(); $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')"); while ($result = db_fetch_object($query)) { if (update_check_incompatibility($result->name, $result->type)) { $incompatible[] = $result->name; drush_log(dt("!type !name is incompatible with this release of Drupal, and will be disabled.", array("!type" => $result->type, '!name' => $result->name)), "warning"); } } if (!empty($incompatible)) { $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('". implode("','", $incompatible) ."')"); } return $ret; } /** * Helper function to test compatibility of a module or theme. */ function update_check_incompatibility($name, $type = 'module') { static $themes, $modules; // Store values of expensive functions for future use. if (empty($themes) || empty($modules)) { drush_include_engine('drupal', 'environment'); $themes = _system_theme_data(); $modules = module_rebuild_cache(); } if ($type == 'module' && isset($modules[$name])) { $file = $modules[$name]; } else if ($type == 'theme' && isset($themes[$name])) { $file = $themes[$name]; } if (!isset($file) || !isset($file->info['core']) || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY || version_compare(phpversion(), $file->info['php']) < 0) { return TRUE; } return FALSE; } /** * Perform Drupal 5.x to 6.x updates that are required for update.php * to function properly. * * This function runs when update.php is run the first time for 6.x, * even before updates are selected or performed. It is important * that if updates are not ultimately performed that no changes are * made which make it impossible to continue using the prior version. * Just adding columns is safe. However, renaming the * system.description column to owner is not. Therefore, we add the * system.owner column and leave it to system_update_6008() to copy * the data from description and remove description. The same for * renaming locales_target.locale to locales_target.language, which * will be finished by locale_update_6002(). */ function update_fix_d6_requirements() { $ret = array(); if (drupal_get_installed_schema_version('system') < 6000 && !variable_get('update_d6_requirements', FALSE)) { $spec = array('type' => 'int', 'size' => 'small', 'default' => 0, 'not null' => TRUE); db_add_field($ret, 'cache', 'serialized', $spec); db_add_field($ret, 'cache_filter', 'serialized', $spec); db_add_field($ret, 'cache_page', 'serialized', $spec); db_add_field($ret, 'cache_menu', 'serialized', $spec); db_add_field($ret, 'system', 'info', array('type' => 'text')); db_add_field($ret, 'system', 'owner', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); if (db_table_exists('locales_target')) { db_add_field($ret, 'locales_target', 'language', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => '')); } if (db_table_exists('locales_source')) { db_add_field($ret, 'locales_source', 'textgroup', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default')); db_add_field($ret, 'locales_source', 'version', array('type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none')); } variable_set('update_d6_requirements', TRUE); // Create the cache_block table. See system_update_6027() for more details. $schema['cache_block'] = array( 'fields' => array( 'cid' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'data' => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'), 'expire' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), 'headers' => array('type' => 'text', 'not null' => FALSE), 'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0) ), 'indexes' => array('expire' => array('expire')), 'primary key' => array('cid'), ); db_create_table($ret, 'cache_block', $schema['cache_block']); // Create the semaphore table now -- the menu system after 6.15 depends on // this table, and menu code runs in updates prior to the table being // created in its original update function, system_update_6054(). $schema['semaphore'] = array( 'fields' => array( 'name' => array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'value' => array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'expire' => array( 'type' => 'float', 'size' => 'big', 'not null' => TRUE), ), 'indexes' => array('expire' => array('expire')), 'primary key' => array('name'), ); db_create_table($ret, 'semaphore', $schema['semaphore']); } return $ret; } /** * Check update requirements and report any errors. */ function update_check_requirements() { // Check the system module requirements only. $requirements = module_invoke('system', 'requirements', 'update'); $severity = drupal_requirements_severity($requirements); // If there are issues, report them. if ($severity != REQUIREMENT_OK) { foreach ($requirements as $requirement) { if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) { $message = isset($requirement['description']) ? $requirement['description'] : ''; if (isset($requirement['value']) && $requirement['value']) { $message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')'; } drush_log($message, 'warning'); } } } } /** * Create the batch table. * * This is part of the Drupal 5.x to 6.x migration. */ function update_create_batch_table() { // If batch table exists, update is not necessary if (db_table_exists('batch')) { return; } $schema['batch'] = array( 'fields' => array( 'bid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), 'token' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE), 'timestamp' => array('type' => 'int', 'not null' => TRUE), 'batch' => array('type' => 'text', 'not null' => FALSE, 'size' => 'big') ), 'primary key' => array('bid'), 'indexes' => array('token' => array('token')), ); $ret = array(); db_create_table($ret, 'batch', $schema['batch']); return $ret; } function update_main_prepare() { global $profile; // Some unavoidable errors happen because the database is not yet up-to-date. // Our custom error handler is not yet installed, so we just suppress them. drush_errors_off(); require_once './includes/bootstrap.inc'; // Minimum load of components. // This differs from the Drupal 6 update.php workflow for compatbility with // the Drupal 6 backport of module_implements() caching. // @see http://drupal.org/node/557542 drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); require_once './includes/install.inc'; require_once './includes/file.inc'; require_once './modules/system/system.install'; // Load module basics. include_once './includes/module.inc'; $module_list['system']['filename'] = 'modules/system/system.module'; $module_list['filter']['filename'] = 'modules/filter/filter.module'; module_list(TRUE, FALSE, FALSE, $module_list); module_implements('', FALSE, TRUE); drupal_load('module', 'system'); drupal_load('module', 'filter'); // Set up $language, since the installer components require it. drupal_init_language(); // Set up theme system for the maintenance page. drupal_maintenance_theme(); // Check the update requirements for Drupal. update_check_requirements(); drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); $profile = variable_get('install_profile', 'default'); // Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6. if (!drush_get_context('DRUSH_USER')) { drush_set_option('user', 1); drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN); } // This must happen *after* drupal_bootstrap(), since it calls // variable_(get|set), which only works after a full bootstrap. _drush_log_update_sql(update_create_batch_table()); // Turn error reporting back on. From now on, only fatal errors (which are // not passed through the error handler) will cause a message to be printed. drush_errors_on(); // Perform Drupal 5.x to 6.x updates that are required for update.php to function properly. _drush_log_update_sql(update_fix_d6_requirements()); // Must unset $theme->status in order to safely rescan and repopulate // the system table to ensure we have a full picture of the platform. // This is needed because $theme->status is set to 0 in a call to // list_themes() done by drupal_maintenance_theme(). // It is a issue with _system_theme_data() that returns its own cache // variable and can be modififed by others. When this is fixed in // drupal core we can remove this unset. // For reference see: http://drupal.org/node/762754 $themes = _system_theme_data(); foreach ($themes as $theme) { unset($theme->status); } drush_get_extensions(); include_once './includes/batch.inc'; drupal_load_updates(); // Disable anything in the {system} table that is not compatible with the current version of Drupal core. _drush_log_update_sql(update_fix_compatibility()); } function update_main() { update_main_prepare(); $start = array(); $has_updates = FALSE; $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); foreach ($modules as $module => $schema_version) { $updates = drupal_get_schema_versions($module); // Skip incompatible module updates completely, otherwise test schema versions. if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) { // module_invoke returns NULL for nonexisting hooks, so if no updates // are removed, it will == 0. $last_removed = module_invoke($module, 'update_last_removed'); if ($schema_version < $last_removed) { drush_set_error('PROVISION_DRUPAL_UPDATE_FAILED', dt( $module .' module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update '. $module .' module, you will first need to upgrade to the last version in which these updates were available.')); continue; } $updates = drupal_map_assoc($updates); foreach (array_keys($updates) as $update) { if ($update > $schema_version) { $start[$module] = $update; break; } } // Record any pending updates. Used for confirmation prompt. foreach (array_keys($updates) as $update) { if ($update > $schema_version) { if (class_exists('ReflectionFunction')) { // The description for an update comes from its Doxygen. $func = new ReflectionFunction($module. '_update_'. $update); $description = str_replace(array("\n", '*', '/'), '', $func->getDocComment()); } if (empty($description)) { $description = dt('description not available'); } $pending[$module][] = array("$update - ". trim($description)); $has_updates = TRUE; } } } } // Print a list of pending updates for this module and get confirmation. if ($has_updates) { drush_print(dt('The following updates are pending:')); drush_print(); foreach ($pending as $module => $updates) { if (sizeof($updates)) { array_unshift($updates, array($module . ' module')); drush_print_table($updates, TRUE); drush_print(); } } if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { return drush_user_abort(); } // Proceed with running all pending updates. $operations = array(); foreach ($start as $module => $version) { drupal_set_installed_schema_version($module, $version - 1); $updates = drupal_get_schema_versions($module); $max_version = max($updates); if ($version <= $max_version) { drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version))); foreach ($updates as $update) { if ($update >= $version) { $operations[] = array('_update_do_one', array($module, $update)); } } } else { drush_log(dt('No database updates for module @module', array('@module' => $module)), 'success'); } } $batch = array( 'operations' => $operations, 'title' => 'Updating', 'init_message' => 'Starting updates', 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', 'finished' => 'update_finished', ); batch_set($batch); $batch =& batch_get(); $batch['progressive'] = FALSE; drush_backend_batch_process('updatedb-batch-process'); } else { drush_log(dt("No database updates required"), 'success'); } } /** * A simplified version of the batch_do_one function from update.php * * This does not mess with sessions and the like, as it will be used * from the command line */ function _update_do_one($module, $number, &$context) { // If updates for this module have been aborted // in a previous step, go no further. if (!empty($context['results'][$module]['#abort'])) { return; } $function = $module .'_update_'. $number; drush_log("Executing $function", 'success'); if (function_exists($function)) { $ret = $function($context['sandbox']); $context['results'][$module] = $ret; _drush_log_update_sql($ret); } if (isset($ret['#finished'])) { $context['finished'] = $ret['#finished']; unset($ret['#finished']); } if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) { drupal_set_installed_schema_version($module, $number); } } function _update_batch_command($id) { update_main_prepare(); drush_batch_command($id); } drush-5.10.0/commands/core/drupal/update_7.inc000066400000000000000000000243261222105546100211520ustar00rootroot00000000000000 FALSE, 'query' => 'What went wrong'); * The schema version will not be updated in this case, and all the * aborted updates will continue to appear on update.php as updates that * have not yet been run. * * @param $module * The module whose update will be run. * @param $number * The update number to run. * @param $context * The batch context array */ function drush_update_do_one($module, $number, $dependency_map, &$context) { $function = $module . '_update_' . $number; // If this update was aborted in a previous step, or has a dependency that // was aborted in a previous step, go no further. if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { return; } $context['log'] = FALSE; $ret = array(); if (function_exists($function)) { try { if ($context['log']) { Database::startLog($function); } drush_log("Executing " . $function); $ret['results']['query'] = $function($context['sandbox']); $ret['results']['success'] = TRUE; } // @TODO We may want to do different error handling for different exception // types, but for now we'll just print the message. catch (Exception $e) { $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); drush_set_error('DRUPAL_EXCEPTION', $e->getMessage()); } if ($context['log']) { $ret['queries'] = Database::getLog($function); } } if (isset($context['sandbox']['#finished'])) { $context['finished'] = $context['sandbox']['#finished']; unset($context['sandbox']['#finished']); } if (!isset($context['results'][$module])) { $context['results'][$module] = array(); } if (!isset($context['results'][$module][$number])) { $context['results'][$module][$number] = array(); } $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); if (!empty($ret['#abort'])) { // Record this function in the list of updates that were aborted. $context['results']['#abort'][] = $function; } // Record the schema update if it was completed successfully. if ($context['finished'] == 1 && empty($ret['#abort'])) { drupal_set_installed_schema_version($module, $number); } $context['message'] = 'Performed update: ' . $function; } /** * Check update requirements and report any errors. */ function update_check_requirements() { $warnings = FALSE; // Check the system module and update.php requirements only. $requirements = system_requirements('update'); $requirements += update_extra_requirements(); // If there are issues, report them. foreach ($requirements as $requirement) { if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) { $message = isset($requirement['description']) ? $requirement['description'] : ''; if (isset($requirement['value']) && $requirement['value']) { $message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')'; } $warnings = TRUE; drupal_set_message($message, 'warning'); } } return $warnings; } function update_main_prepare() { // Some unavoidable errors happen because the database is not yet up-to-date. // Our custom error handler is not yet installed, so we just suppress them. drush_errors_off(); // We prepare a minimal bootstrap for the update requirements check to avoid // reaching the PHP memory limit. $core = DRUSH_DRUPAL_CORE; require_once $core . '/includes/bootstrap.inc'; require_once $core . '/includes/common.inc'; require_once $core . '/includes/file.inc'; require_once $core . '/includes/entity.inc'; include_once $core . '/includes/unicode.inc'; update_prepare_d7_bootstrap(); drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); require_once $core . '/includes/install.inc'; require_once $core . '/modules/system/system.install'; // Load module basics. include_once $core . '/includes/module.inc'; $module_list['system']['filename'] = 'modules/system/system.module'; module_list(TRUE, FALSE, FALSE, $module_list); drupal_load('module', 'system'); // Reset the module_implements() cache so that any new hook implementations // in updated code are picked up. module_implements('', FALSE, TRUE); // Set up $language, since the installer components require it. drupal_language_initialize(); // Set up theme system for the maintenance page. drupal_maintenance_theme(); // Check the update requirements for Drupal. update_check_requirements(); // update_fix_d7_requirements() needs to run before bootstrapping beyond path. // So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc. drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE); update_fix_d7_requirements(); // Now proceed with a full bootstrap. drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); drupal_maintenance_theme(); drush_errors_on(); include_once DRUPAL_ROOT . '/includes/batch.inc'; drupal_load_updates(); update_fix_compatibility(); // Change query-strings on css/js files to enforce reload for all users. _drupal_flush_css_js(); // Flush the cache of all data for the update status module. if (db_table_exists('cache_update')) { cache_clear_all('*', 'cache_update', TRUE); } module_list(TRUE, FALSE, TRUE); } function update_main() { update_main_prepare(); $pending = update_get_update_list(); $start = array(); // Ensure system module's updates run first. $start['system'] = array(); // Print a list of pending updates for this module and get confirmation. if (sizeof($pending)) { drush_print(dt('The following updates are pending:')); drush_print(); foreach ($pending as $module => $updates) { if (isset($updates['start'])) { drush_print($module . ' module : '); if (isset($updates['start'])) { $start[$module] = $updates['start']; foreach ($updates['pending'] as $update) { drush_print($update, 2); } } drush_print(); } } if (!drush_confirm(dt('Do you wish to run all pending updates?'))) { return drush_user_abort(); } drush_update_batch($start); } else { drush_log(dt("No database updates required"), 'success'); } } function _update_batch_command($id) { update_main_prepare(); drush_batch_command($id); } /** * Start the database update batch process. * * @param $start * An array of all the modules and which update to start at. * @param $redirect * Path to redirect to when the batch has finished processing. * @param $url * URL of the batch processing page (should only be used for separate * scripts like update.php). * @param $batch * Optional parameters to pass into the batch API. * @param $redirect_callback * (optional) Specify a function to be called to redirect to the progressive * processing page. */ function drush_update_batch($start) { // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $updates = update_resolve_dependencies($start); // Store the dependencies for each update function in an array which the // batch API can pass in to the batch operation each time it is called. (We // do not store the entire update dependency array here because it is // potentially very large.) $dependency_map = array(); foreach ($updates as $function => $update) { $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); } $operations = array(); foreach ($updates as $update) { if ($update['allowed']) { // Set the installed version of each module so updates will start at the // correct place. (The updates are already sorted, so we can simply base // this on the first one we come across in the above foreach loop.) if (isset($start[$update['module']])) { drupal_set_installed_schema_version($update['module'], $update['number'] - 1); unset($start[$update['module']]); } // Add this update function to the batch. $function = $update['module'] . '_update_' . $update['number']; $operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); } } $batch['operations'] = $operations; $batch += array( 'title' => 'Updating', 'init_message' => 'Starting updates', 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', 'finished' => 'drush_update_finished', 'file' => 'includes/update.inc', ); batch_set($batch); drush_backend_batch_process('updatedb-batch-process'); } function drush_update_finished($success, $results, $operations) { // Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback. } drush-5.10.0/commands/core/field.drush.inc000066400000000000000000000261711222105546100203620ustar00rootroot00000000000000 'Create fields and instances. Returns urls for field editing.', 'core' => array('7+'), 'drupal_dependencies' => array('field_ui'), 'arguments' => array( 'bundle' => 'Content type (for nodes). Name of bundle to attach fields to. Required.', 'field_spec' => 'Comma delimited triple in the form: field_name,field_type,widget_name. If widget_name is omitted, the default widget will be used. Separate multiple fields by space. If omitted, a wizard will prompt you.' ), 'required-arguments' => 1, 'options' => array( 'entity_type' => 'Type of entity (e.g. node, user, comment). Defaults to node.', ), 'examples' => array( 'drush field-create article' => 'Define new article fields via interactive prompts.', 'open `drush field-create article`' => 'Define new article fields and then open field edit form for refinement.', 'drush field-create article city,text,text_textfield subtitle,text,text_textfield' => 'Create two new fields.' ), ); $items['field-update'] = array( 'description' => 'Return URL for field editing web page.', 'core' => array('7+'), 'drupal_dependencies' => array('field_ui'), 'arguments' => array( 'field_name' => 'Name of field that needs updating.', ), 'examples' => array( 'field-update comment_body' => 'Quickly navigate to a field edit web page.', ), ); $items['field-delete'] = array( 'description' => 'Delete a field and its instances.', 'core' => array('7+'), 'arguments' => array( 'field_name' => 'Name of field to delete.', ), 'options' => array( 'bundle' => 'Only delete the instance attached to this bundle. If omitted, admin can choose to delete one instance or whole field.', 'entity_type' => 'Disambiguate a particular bundle from identically named bundles. Usually not needed.' ), 'examples' => array( 'field-delete city' => 'Delete the city field and any instances it might have.', 'field-delete city --bundle=article' => 'Delete the city instance on the article bundle', ), ); $items['field-clone'] = array( 'description' => 'Clone a field and all its instances.', 'core' => array('7+'), 'arguments' => array( 'source_field_name' => 'Name of field that will be cloned', 'target_field_name' => 'Name of new, cloned field.', ), 'examples' => array( 'field-clone tags labels' => 'Copy \'tags\' field into a new field \'labels\' field which has same instances.', 'open `field-clone tags labels`' => 'Clone field and then open field edit forms for refinement.', ), ); $items['field-info'] = array( 'description' => 'View information about fields, field_types, and widgets.', 'drupal_dependencies' => array('field_ui'), 'core' => array('7+'), 'arguments' => array( 'type' => 'Recognized values: fields, types. If omitted, a choice list appears.', ), 'options' => array( 'pipe' => 'Return field information table as CSV.', ), 'examples' => array( 'field-info types' => 'Show a table which lists all field types and their available widgets', ), ); return $items; } /** * Command argument complete callback. */ function field_field_create_complete() { if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { $all = array(); $info = field_info_bundles(); foreach ($info as $entity_type => $bundles) { $all = array_merge($all, array_keys($bundles)); } return array('values' => array_unique($bundles)); } } /** * Command argument complete callback. */ function field_field_update_complete() { return field_field_complete_field_names(); } /** * Command argument complete callback. */ function field_field_delete_complete() { return field_field_complete_field_names(); } /** * Command argument complete callback. */ function field_field_clone_complete() { return field_field_complete_field_names(); } /** * Command argument complete callback. */ function field_field_info_complete() { return array('values' => array('fields', 'types')); } /** * List field names for completion. * * @return * Array of available site aliases. */ function field_field_complete_field_names() { if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { $info = field_info_fields(); return array('values' => array_keys($info)); } } function drush_field_create($bundle) { $entity_type = drush_get_option('entity_type', 'node'); $args = func_get_args(); array_shift($args); if (empty($args)) { // Just one item in this array for now. $args[] = drush_field_create_wizard(); } // Iterate over each field spec. foreach ($args as $string) { list($name, $type, $widget) = explode(',', $string); $info = field_info_field($name); if (empty($info)) { // Field does not exist already. Create it. $field = array( 'field_name' => $name, 'type' => $type, ); drush_op('field_create_field', $field); } // Create the instance. $instance = array( 'field_name' => $name, 'entity_type' => $entity_type, 'bundle' => $bundle, ); if ($widget) { $instance['widget'] = array('type' => $widget); } drush_op('field_create_instance', $instance); $urls[] = url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $name, array('absolute' => TRUE)); } drush_print(implode(' ', $urls)); } // Copy of function _field_ui_bundle_admin_path() since we don't want to load UI module. function drush_field_ui_bundle_admin_path($entity_type, $bundle_name) { $bundles = field_info_bundles($entity_type); $bundle_info = $bundles[$bundle_name]; if (isset($bundle_info['admin'])) { return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path']; } } function drush_field_update($field_name) { $info = field_info_field($field_name); foreach ($info['bundles'] as $entity_type => $bundles) { foreach ($bundles as $bundle) { $urls[] = url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $field_name, array('absolute' => TRUE)); } } drush_print(implode(' ', $urls)); } function drush_field_delete($field_name) { $info = field_info_field($field_name); $confirm = TRUE; if (!$bundle = drush_get_option('bundle')) { foreach ($info['bundles'] as $entity_type => $bundles) { foreach ($bundles as $bundle) { $all_bundles[] = $bundle; } } if (count($bundles) > 1) { $options = array_merge(array('all' => dt('All bundles')), drupal_map_assoc($bundles)); $bundle = drush_choice($options, dt("Choose a particular bundle or 'All bundles'")); $confirm = FALSE; } else { if (!drush_confirm(dt('Do you want to delete the !field_name field?', array('!field_name' => $field_name)))) { return drush_user_abort(); } } } if ($bundle == 'all') { foreach ($info['bundles'] as $entity_type => $bundles) { foreach ($bundles as $bundle) { $instance = field_info_instance($entity_type, $field_name, $bundle); drush_op('field_delete_instance', $instance); } } } else { $entity_type = drush_field_get_entity_from_bundle($bundle); $instance = field_info_instance($entity_type, $field_name, $bundle); drush_op('field_delete_instance', $instance); } // If there are no more bundles, delete the field. $info = field_info_field($field_name); if (empty($info['bundles'])) { drush_op('field_delete_field', $field_name); } } function drush_field_clone($source_field_name, $target_field_name) { if (!$info = field_info_field($source_field_name)) { return drush_set_error(dt('!source not found in field list.', array('!source' => $source_field_name))); } unset($info['id']); $info['field_name'] = $target_field_name; $target = drush_op('field_create_field', $info); foreach ($info['bundles'] as $entity_type => $bundles) { foreach ($bundles as $bundle) { $instance = field_info_instance($entity_type, $source_field_name, $bundle); $instance['field_name'] = $target_field_name; unset($instance['id']); $instance['field_id'] = $target['id']; drush_op('field_create_instance', $instance); $urls[] = url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $target_field_name, array('absolute' => TRUE)); } } drush_print(implode(' ', $urls)); } function drush_field_info($type = NULL) { if (is_null($type)) { $type = drush_choice(drupal_map_assoc(array('types', 'fields')), dt('Which information do you wish to see?')); } switch ($type) { case 'fields': $rows[] = array( dt('Field name'), dt('Field type'), dt('Bundles'), ); $info = field_info_fields(); foreach ($info as $field_name => $field) { $bundle_strs = array(); foreach ($field['bundles'] as $entity_type => $bundles) { $bundle_strs[] = implode(',', $bundles); } $row = array( $field_name, $field['type'], implode(' ', $bundle_strs), ); $rows[] = $row; $pipe[] = implode(',', $row); } break; case 'types': $rows[] = array( dt('Field type'), dt('Default widget'), dt('Widgets'), ); $info = field_info_field_types(); module_load_include('inc', 'field_ui', 'field_ui.admin'); $widgets = field_info_widget_types(); foreach ($info as $type_name => $type) { $widgets = field_ui_widget_type_options($type_name); $row = array( $type_name, $type['default_widget'], implode(', ', array_keys($widgets)), ); $rows[] = $row; $pipe[] = implode(',', $row); } break; } drush_print_table($rows, TRUE); drush_print_pipe($pipe); return $rows; } /** * Prompt user enough to create basic field and instance. * * @return array $field_spec * An array of brief field specifications. */ function drush_field_create_wizard() { $specs[] = drush_prompt(dt('Field name')); module_load_include('inc', 'field_ui', 'field_ui.admin'); $types = field_ui_field_type_options(); $field_type = drush_choice($types, dt('Choose a field type')); $specs[] = $field_type; $widgets = field_ui_widget_type_options($field_type); $specs[] = drush_choice($widgets, dt('Choose a widget')); return implode(',', $specs); } function drush_field_get_entity_from_bundle($bundle) { if (drush_get_option('entity_type')) { return drush_get_option('entity_type'); } else { $info = field_info_bundles(); foreach ($info as $entity_type => $bundles) { if (isset($bundles[$bundle])) { return $entity_type; } } } } drush-5.10.0/commands/core/help.drush.inc000066400000000000000000000454511222105546100202310ustar00rootroot00000000000000 $commandstring, '!name' => $shell_aliases[$commandstring])); drush_log($msg, 'ok'); return TRUE; } return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring))); } /** * Print the help for a single command to the screen. * * @param array $command * A fully loaded $command array. */ function drush_print_help($command) { _drush_help_merge_subcommand_information($command); if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) { $help = array($command['description']); } if ($command['strict-option-handling']) { $command['topics'][] = 'docs-strict-options'; } // Give commandfiles an opportunity to add examples and options to the command. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); drush_command_invoke_all_ref('drush_help_alter', $command); drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80))); drush_print(); // Sort command options. uksort($command['options'], '_drush_help_sort_command_options'); // Print command sections help. foreach ($command['sections'] as $key => $value) { if (!empty($command[$key])) { drush_print(dt($value) . ':'); $rows = drush_format_help_section($command, $key); drush_print_table($rows, FALSE, array(40)); unset($rows); drush_print(); } } // Append aliases if any. if ($command['aliases']) { drush_print(dt("Aliases: ") . implode(', ', $command['aliases'])); } } /** * Sort command options alphabetically. Engine options at the end. */ function _drush_help_sort_command_options($a, $b) { $engine_a = strpos($a, '='); $engine_b = strpos($b, '='); if ($engine_a && !$engine_b) { return 1; } else if (!$engine_a && $engine_b) { return -1; } elseif ($engine_a && $engine_b) { if (substr($a, 0, $engine_a) == substr($b, 0, $engine_b)) { return 0; } } return ($a < $b) ? -1 : 1; } /** * Check to see if the specified command contains an 'allow-additional-options' * record. If it does, find the additional options that are allowed, and * add in the help text for the options of all of the sub-commands. */ function _drush_help_merge_subcommand_information(&$command) { // 'allow-additional-options' will either be FALSE (default), // TRUE ("allow anything"), or an array that lists subcommands // that are or may be called via drush_invoke by this command. if (is_array($command['allow-additional-options'])) { $implemented = drush_get_commands(); foreach ($command['allow-additional-options'] as $subcommand_name) { if (array_key_exists($subcommand_name, $implemented)) { $command['options'] += $implemented[$subcommand_name]['options']; $command['sub-options'] = array_merge_recursive($command['sub-options'], $implemented[$subcommand_name]['sub-options']); if (empty($command['arguments'])) { $command['arguments'] = $implemented[$subcommand_name]['arguments']; } $command['topics'] = array_merge($command['topics'], $implemented[$subcommand_name]['topics']); } } } } /** * Format one named help section from a command record * * @param $command * A command record with help information * @param $section * The name of the section to format ('options', 'topic', etc.) * @returns array * Formatted rows, suitable for printing via drush_print_table. */ function drush_format_help_section($command, $section) { $formatter = (function_exists('drush_help_section_formatter_' . $section)) ? 'drush_help_section_formatter_' . $section : 'drush_help_section_default_formatter'; foreach ($command[$section] as $name => $help_attributes) { if (!is_array($help_attributes)) { $help_attributes = array('description' => $help_attributes); } $help_attributes['label'] = $name; call_user_func_array($formatter, array($command, &$help_attributes)); if (!array_key_exists('hidden', $help_attributes)) { $rows[] = array($help_attributes['label'], $help_attributes['description']); // Process the subsections too, if any if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) { $rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter)); } } } return $rows; } /** * Format one named portion of a subsection from a command record. * Subsections allow related parts of a help record to be grouped * together. For example, in the 'options' section, sub-options that * are related to a particular primary option are stored in a 'sub-options' * section whose name == the name of the primary option. * * @param $command * A command record with help information * @param $section * The name of the section to format ('options', 'topic', etc.) * @param $subsection * The name of the subsection (e.g. the name of the primary option) * @param $formatter * The name of a function to use to format the rows of the subsection * @param $prefix * Characters to prefix to the front of the label (for indentation) * @returns array * Formatted rows, suitable for printing via drush_print_table. */ function _drush_format_help_subsection($command, $section, $subsection, $formatter, $prefix = ' ') { foreach ($command['sub-' . $section][$subsection] as $name => $help_attributes) { if (!is_array($help_attributes)) { $help_attributes = array('description' => $help_attributes); } $help_attributes['label'] = $name; call_user_func_array($formatter, array($command, &$help_attributes)); if (!array_key_exists('hidden', $help_attributes)) { $rows[] = array($prefix . $help_attributes['label'], $help_attributes['description']); // Process the subsections too, if any if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) { $rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter, $prefix . ' ')); } } } return $rows; } /** * The options section formatter. Adds a "--" in front of each * item label. Also handles short-form and example-value * components in the help attributes. */ function drush_help_section_formatter_options($command, &$help_attributes) { if ($help_attributes['label'][0] == '-') { drush_log(dt("Option '!option' of command !command should instead be declared as '!fixed'", array('!option' => $help_attributes['label'], '!command' => $command['command'], '!fixed' => preg_replace('/^--*/', '', $help_attributes['label']))), 'debug'); } else { $help_attributes['label'] = '--' . $help_attributes['label']; } if (array_key_exists('required', $help_attributes)) { $help_attributes['description'] .= " " . dt("Required."); } $prefix = '<'; $suffix = '>'; if (array_key_exists('example-value', $help_attributes)) { if (isset($help_attributes['value']) && $help_attributes['value'] == 'optional') { $prefix = '['; $suffix = ']'; } $help_attributes['label'] .= '=' . $prefix . $help_attributes['example-value'] . $suffix; if (array_key_exists('short-form', $help_attributes)) { $help_attributes['short-form'] .= " $prefix" . $help_attributes['example-value'] . $suffix; } } if (array_key_exists('short-form', $help_attributes)) { $help_attributes['label'] = '-' . $help_attributes['short-form'] . ', ' . $help_attributes['label']; } drush_help_section_default_formatter($command, $help_attributes); } /** * The default section formatter. Replaces '[command]' with the * command name. */ function drush_help_section_default_formatter($command, &$help_attributes) { // '[command]' is a token representing the current command. @see pm_drush_engine_version_control(). $help_attributes['label'] = str_replace('[command]', $command['command'], $help_attributes['label']); } /** * Build a fake command for the purposes of showing examples and options. */ function drush_global_options_command($brief = FALSE) { $global_options_help = array( 'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help. Run `drush topic` to read even more documentation.', 'sections' => array( 'options' => 'Global options (see `drush topic core-global-options` for the full list)', ), 'options' => drush_get_global_options($brief), 'examples' => array( 'drush dl cck zen' => 'Download CCK module and Zen theme.', 'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.', ), '#brief' => TRUE, ); $global_options_help += drush_command_defaults('global-options', 'global_options', __FILE__); drush_command_invoke_all_ref('drush_help_alter', $global_options_help); return $global_options_help; } /** * Command callback for help command. This is the default command, when none * other has been specified. */ function drush_core_help() { $commands = func_get_args(); $format = drush_get_option('format'); // Backwards compatibility for --html option. if (drush_get_option('html')) { $format = 'html'; } if (empty($commands)) { // For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); $implemented = drush_get_commands(); ksort($implemented); $command_categories = drush_help_categorize($implemented); $visible = drush_help_visible($command_categories); // If the user specified --filter w/out a value, then // present a choice list of help categories. if (drush_get_option('filter', FALSE) === TRUE) { $help_categories = array(); foreach ($command_categories as $key => $info) { $description = $info['title']; if (array_key_exists('summary', $info)) { $description .= ": " . $info['summary']; } $help_categories[$key] = $description; } $result = drush_choice($help_categories, 'Select a help category:'); if (!$result) { return drush_user_abort(); } drush_set_option('filter', $result); } // Filter out categories that the user does not want to see $filter_category = drush_get_option('filter'); if (!empty($filter_category) && ($filter_category !== TRUE)) { if (!array_key_exists($filter_category, $command_categories)) { return drush_set_error('DRUSH_NO_CATEGORY', dt("The specified command category !filter does not exist.", array('!filter' => $filter_category))); } $command_categories = array($filter_category => $command_categories[$filter_category]); } if ($format == 'html') { drush_print(drush_help_html_header()); } // Make a fake command section to hold the global options, then print it. $global_options_help = drush_global_options_command(TRUE); if (!in_array($format, array('html', 'export', 'json')) && !drush_get_option('filter')) { drush_print_help($global_options_help); } if ($format == 'json' || $format == 'export') { // Print giant array and return. drush_print(drush_format($command_categories, 'commands')); return $command_categories; } else { // HTML and CLI command listing (in a table). drush_help_listing_print($command_categories, $format); if ($format == 'html') { // Print global options and long-form help for all commands. drush_print(drush_help_html_global_options($global_options_help)); drush_print(drush_help_html($visible)); } } // Newline-delimited list for use by other scripts. Set the --pipe option. if (drush_get_option('pipe')) { drush_print_pipe(implode("\n", array_keys($visible))); } return; } else { $result = TRUE; while ((count($commands) > 0) && !drush_get_error()) { $result = drush_show_help(array_shift($commands)); } return $result; } return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => implode(" ", $commands)))); } // Uncategorize the list of commands. Hiddens have been removed and // filtering performed. function drush_help_visible($command_categories) { $all = array(); foreach ($command_categories as $category => $info) { $all = array_merge($all, $info['commands']); } return $all; } /* * Print CLI table or HTML table listing all commands. */ function drush_help_listing_print($command_categories, $format) { $all_commands = array(); foreach ($command_categories as $key => $info) { // Get the commands in this category. $commands = $info['commands']; // Build rows for drush_print_table(). $rows = array(); foreach($commands as $cmd => $command) { $name = $command['aliases'] ? $cmd . ' (' . implode(', ', $command['aliases']) . ')': $cmd; $rows[$cmd] = array($name, $command['description']); } // Vary the output by mode: CLI or HTML if ($format == 'html') { drush_print("

" . $info['title'] . "

"); drush_print(drush_help_html_command_list($commands)); } else { drush_print($info['title'] . ": (" . $key . ")"); drush_print_table($rows, FALSE, array(0 => 20)); } } } /* * Organize commands into categories */ function drush_help_categorize($implemented) { $command_categories = array(); $category_map = array(); foreach ($implemented as $key => $candidate) { if ((!array_key_exists('is_alias', $candidate) || !$candidate['is_alias']) && !$candidate['hidden']) { $category = $candidate['commandfile']; // If we have decided to remap a category, remap every command if (array_key_exists($category, $category_map)) { $category = $category_map[$category]; } if (!array_key_exists($category, $command_categories)) { $title = drush_command_invoke_all('drush_help', "meta:$category:title"); $alternate_title = ''; if (!$title) { // If there is no title, then check to see if the // command file is stored in a folder with the same // name as some other command file (e.g. 'core') that // defines a title. $alternate = basename($candidate['path']); $alternate_title = drush_command_invoke_all('drush_help', "meta:$alternate:title"); } if (!empty($alternate_title)) { $category_map[$category] = $alternate; $category = $alternate; $title = $alternate_title; } $command_categories[$category]['title'] = empty($title) ? '' : $title[0]; $summary = drush_command_invoke_all('drush_help', "meta:$category:summary"); if ($summary) { $command_categories[$category]['summary'] = $summary[0]; } } $command_categories[$category]['commands'][$key] = $candidate; } } // Make sure that 'core' is always first in the list $core_category = array('core' => $command_categories['core']); unset($command_categories['core']); // Post-process the categories that have no title. // Any that have fewer than 4 commands go into a section called "other". $processed_categories = array(); $misc_categories = array(); $other_commands = array(); $other_categories = array(); foreach ($command_categories as $key => $info) { if (empty($info['title'])) { $one_category = $key; if (count($info['commands']) < 4) { $other_commands = array_merge($other_commands, $info['commands']); $other_categories[] = $one_category; } else { $info['title'] = dt("All commands in !category", array('!category' => $key)); $misc_categories[$one_category] = $info; } } else { $processed_categories[$key] = $info; } } $other_category = array(); if (!empty($other_categories)) { $other_category[implode(',', $other_categories)] = array('title' => dt("Other commands"), 'commands' => $other_commands); } asort($processed_categories); asort($misc_categories); $command_categories = array_merge($core_category, $processed_categories, $misc_categories, $other_category); // If the user specified --sort, then merge all of the remaining // categories together if (drush_get_option('sort', FALSE)) { $combined_commands = array(); foreach ($command_categories as $key => $info) { $combined_commands = array_merge($combined_commands, $info['commands']); } $command_categories = array('all' => array('commands' => $combined_commands, 'title' => dt("Commands:"))); } return $command_categories; } /** * Return an HTML page header that includes all global options. */ function drush_help_html_header() { $output = "drush help\n"; } function drush_help_html_global_options($global_options_help) { // Global options $global_option_rows = drush_format_help_section($global_options_help, 'options'); $output = '

Global Options (see `drush topic core-global-options` for the full list)

'; foreach ($global_option_rows as $row) { $output .= ""; foreach ($row as $value) { $output .= "\n"; } $output .= ""; } $output .= "
" . htmlspecialchars($value) . "
\n"; return $output; } function drush_help_html_command_list($commands) { // Command table $output = "\n"; foreach ($commands as $key => $command) { $output .= " \n"; } $output .= "
$key" . $command['description'] . "
\n"; return $output; } /** * Return an HTML page documenting all available commands and global options. */ function drush_help_html($commands) { // Command details $output = '

Command detail

'; foreach ($commands as $key => $command) { $output .= "\n
$key
\n";
    ob_start();
    drush_show_help($key);
    $output .= ob_get_clean();
    $output .=  "
\n"; } $output .= "\n"; return $output; } drush-5.10.0/commands/core/image.drush.inc000066400000000000000000000041551222105546100203570ustar00rootroot00000000000000 'Flush all derived images for a given style.', 'core' => array('7+'), 'drupal_dependencies' => array('image'), 'arguments' => array( 'style' => 'An image style machine name. If not provided, user may choose from a list of names.', ), 'options' => array( 'all' => 'Flush all derived images', ), 'examples' => array( 'drush image-flush' => 'Pick an image style and then delete its images.', 'drush image-flush thumbnail' => 'Delete all thumbnail images.', 'drush image-flush --all' => 'Flush all derived images. They will be regenerated on the fly.', ), ); return $items; } /** * Command argument complete callback. * * @return * Array of available configuration files for editing. */ function image_image_flush_complete() { drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL); return array('values' => array_keys(image_styles())); } function drush_image_flush($style_name = NULL) { if (drush_get_option('all')) { drush_image_flush_all(); } elseif (empty($style_name)) { $choices = drupal_map_assoc(array_keys(image_styles())); $choices = array_merge(array('all' => 'all'), $choices); $style_name = drush_choice($choices, dt("Choose a style to flush.")); if ($style_name == 'all') { drush_image_flush_all(); } else { return drush_invoke('image-flush', array($style_name)); } } else { if ($style = image_style_load($style_name)) { image_style_flush($style); drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), 'success'); } else { return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name))); } } } function drush_image_flush_all() { foreach (image_styles() as $style) { image_style_flush($style); } drush_log(dt('All image styles flushed'), 'success'); } drush-5.10.0/commands/core/queue.drush.inc000066400000000000000000000104631222105546100204200ustar00rootroot00000000000000 'Run a specific queue by name', 'arguments' => array( 'queue_name' => 'The name of the queue to run, as defined in either hook_queue_info or hook_cron_queue_info.', ), 'required-arguments' => TRUE, ); $items['queue-list'] = array( 'description' => 'Returns a list of all defined queues', 'options' => array( 'pipe' => 'Return a comma delimited list of queues.' ) ); return $items; } /** * Validation for Drupal 6 to ensure the drupal_queue module is enabled. * * @param $command * The command being validated. */ function drush_queue_validate($command) { if (drush_drupal_major_version() == 6) { if (!module_exists('drupal_queue')) { $args = array('!command' => $command, '!dependencies' => 'drupal_queue'); return drush_set_error('DRUSH_COMMAND_DEPENDENCY_ERROR', dt('Command !command needs the following modules installed/enabled to run: !dependencies.', $args)); } else { drupal_queue_include(); } } } /** * Validation callback for drush queue-run. */ function drush_queue_run_validate($queue_name) { drush_queue_validate('queue-run'); // Get all queues. $queues = drush_queue_get_queues(); if (!isset($queues[$queue_name])) { return drush_set_error('DRUSH_QUEUE_ERROR', dt('Could not find the !name queue.', array('!name' => $queue_name))); } } /** * Validation callback for drush queue-list. */ function drush_queue_list_validate() { drush_queue_validate('queue-list'); } /** * Command callback for drush queue-run. * * Queue runner that is compatible with queues declared using both * hook_queue_info() and hook_cron_queue_info(). * * @param $queue_name * Arbitrary string. The name of the queue to work with. */ function drush_queue_run($queue_name) { // Get all queues. $queues = drush_queue_get_queues(); // Try to increase the maximum execution time if it is too low. $max_execution_time = ini_get('max_execution_time'); if ($max_execution_time > 0 && $max_execution_time < 240 && !ini_get('safe_mode')) { set_time_limit(240); } $info = $queues[$queue_name]; $function = $info['cron']['callback']; $end = time() + (isset($info['cron']['time']) ? $info['cron']['time'] : 15); $queue = DrupalQueue::get($queue_name); while (time() < $end && ($item = $queue->claimItem())) { $function($item->data); $queue->deleteItem($item); } } /** * Command callback for drush queue-list. */ function drush_queue_list() { $queues = drush_queue_get_queues(); $default_class = variable_get('queue_default_class', 'SystemQueue'); $rows = array(array('Queue', 'Items', 'Class')); foreach (array_keys($queues) as $name) { $q = DrupalQueue::get($name); $class = variable_get('queue_class_' . $name, $default_class); $rows[] = array($name, $q->numberOfItems(), $class); } if (drush_get_option('pipe')) { $pipe = array(); array_shift($rows); foreach ($rows as $r) { $pipe[] = implode(",", $r); } drush_print_pipe($pipe); } else { drush_print_table($rows, TRUE); } // Return the result for backend invoke return $rows; } /** * Get queues defined with hook_cron_queue_info(). * * @return * Array of queues indexed by name and containing queue object and number * of items. */ function drush_queue_get_queues() { static $queues; if (!isset($queues)) { // Invoke hook_queue_info(), currently only defined by the queue_ui module. $queues = module_invoke_all('queue_info'); // Merge in queues from modules that implement hook_cron_queue_info(). $cron_queues = module_invoke_all('cron_queue_info'); drupal_alter('cron_queue_info', $cron_queues); foreach($cron_queues as $name => $queue) { $queues[$name] = array( 'cron' => array( 'callback' => $queue['worker callback'], 'time' => $queue['time'], ) ); } } return $queues; } drush-5.10.0/commands/core/rsync.core.inc000066400000000000000000000220331222105546100202310ustar00rootroot00000000000000 $source))); } if (!isset($destination_settings)) { return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate destination path !path.', array('!path' => $destination))); } // Check to see if this is an rsync multiple command (multiple sources and multiple destinations) $is_multiple = drush_do_multiple_command('rsync', $source_settings, $destination_settings, TRUE); if ($is_multiple === FALSE) { // If the user path is the same for the source and the destination, then // always add a slash to the end of the source. If the user path is not // the same in the source and the destination, then you need to know how // rsync paths work, and put on the trailing '/' if you want it. if ($source_settings['user-path'] == $destination_settings['user-path']) { $source_path .= '/'; } // Prompt for confirmation. This is destructive. if (!drush_get_context('DRUSH_SIMULATE')) { drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path))); if (!drush_confirm(dt('Do you really want to continue?'))) { // was: return drush_set_error('CORE_SYNC_ABORT', 'Aborting.'); return drush_user_abort(); } } // Exclude settings is the default only when both the source and // the destination are aliases or site names. Therefore, include // settings will be the default whenever either the source or the // destination contains a : or a /. $include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE); $options = _drush_build_rsync_options($additional_options, $include_settings_is_default); // Get all of the args and options that appear after the command name. $original_args = drush_get_original_cli_args_and_options(); foreach ($original_args as $original_option) { if ($original_option{0} == '-') { $options .= ' ' . $original_option; } } // Go ahead and call rsync with the paths we determined return drush_core_exec_rsync($source_path, $destination_path, $options); } } /** * Make a direct call to rsync after the source and destination paths * have been evaluated. * * @param $source * Any path that can be passed to rsync. * @param $destination * Any path that can be passed to rsync. * @param $additional_options * An array of options that overrides whatever was passed in on the command * line (like the 'process' context, but only for the scope of this one * call). * @param $include_settings_is_default * If TRUE, then settings.php will be transferred as part of the rsync unless * --exclude-conf is specified. If FALSE, then settings.php will be excluded * from the transfer unless --include-conf is specified. * @param $live_output * If TRUE, output goes directly to the terminal using system(). If FALSE, * rsync is executed with drush_shell_exec() with output in * drush_shell_exec_output(). * * @return * TRUE on success, FALSE on failure. */ function drush_core_call_rsync($source, $destination, $additional_options = array(), $include_settings_is_default = TRUE, $live_output = TRUE) { $options = _drush_build_rsync_options($additional_options, $include_settings_is_default); return drush_core_exec_rsync($source, $destination, $options, $additional_options, $live_output); } function drush_core_exec_rsync($source, $destination, $options, $additional_options = array(), $live_output = TRUE) { $ssh_options = drush_get_option_override($additional_options, 'ssh-options', ''); $exec = "rsync -e 'ssh $ssh_options' $options $source $destination"; if ($live_output) { $exec_result = drush_op_system($exec); $result = ($exec_result == 0); } else { $result = drush_shell_exec($exec); } if (!$result) { drush_set_error('DRUSH_RSYNC_FAILED', dt("Could not rsync from !source to !dest", array('!source' => $source, '!dest' => $destination))); } return $result; } function _drush_build_rsync_options($additional_options, $include_settings_is_default = TRUE) { $options = ''; // Exclude vcs reserved files. if (!_drush_rsync_option_exists('include-vcs', $additional_options)) { $vcs_files = drush_version_control_reserved_files(); foreach ($vcs_files as $file) { $options .= ' --exclude="'.$file.'"'; } } else { unset($additional_options['include-vcs']); } $mode = '-akz'; // Process --include-paths and --exclude-paths options the same way foreach (array('include', 'exclude') as $include_exclude) { // Get the option --include-paths or --exclude-paths and explode to an array of paths // that we will translate into an --include or --exclude option to pass to rsync $inc_ex_path = explode(PATH_SEPARATOR, drush_get_option($include_exclude . '-paths', '')); foreach ($inc_ex_path as $one_path_to_inc_ex) { if (!empty($one_path_to_inc_ex)) { $options .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"'; } } // Remove stuff inserted by evaluate path unset($additional_options[$include_exclude . '-paths']); unset($additional_options[$include_exclude . '-files-processed']); } // drush_core_rsync passes in $include_settings_is_default such that // 'exclude-conf' is the default when syncing from one alias to // another, and 'include-conf' is the default when a path component // is included. if ($include_settings_is_default ? _drush_rsync_option_exists('exclude-conf', $additional_options) : !_drush_rsync_option_exists('include-conf', $additional_options)) { $options .= ' --exclude="settings.php"'; unset($additional_options['exclude-conf']); } if (_drush_rsync_option_exists('exclude-sites', $additional_options)) { $options .= ' --include="sites/all" --exclude="sites/*"'; unset($additional_options['exclude-sites']); } if (_drush_rsync_option_exists('mode', $additional_options)) { $mode = "-" . drush_get_option_override($additional_options, 'mode'); unset($additional_options['mode']); } if (drush_get_context('DRUSH_VERBOSE')) { // the drush_op() will be verbose about the command that gets executed. $mode .= 'v'; $options .= ' --stats --progress'; } // Check if the user has set $options['rsync-version'] to enable rsync legacy version support. // Drush was written for rsync 2.6.9 or later, so assume that version if nothing was explicitly set. $rsync_version = drush_get_option(array('rsync-version','source-rsync-version','target-rsync-version'), '2.6.9'); $options_to_exclude = array('ssh-options'); foreach ($additional_options as $test_option => $value) { // Downgrade some options for older versions of rsync if ($test_option == 'remove-source-files') { if (version_compare($rsync_version, '2.6.4', '<')) { $test_option = NULL; drush_log('Rsync does not support --remove-sent-files prior to version 2.6.4; some temporary files may remain undeleted.', 'warning'); } elseif (version_compare($rsync_version, '2.6.9', '<')) { $test_option = 'remove-sent-files'; } } if ((isset($test_option)) && !in_array($test_option, $options_to_exclude) && (isset($value) && !is_array($value))) { if (($value === TRUE) || (!isset($value))) { $options .= " --$test_option"; } else { $options .= " --$test_option=" . escapeshellarg($value); } } } return $mode . $options; } function _drush_rsync_option_exists($option, $additional_options) { if (array_key_exists($option, $additional_options)) { return TRUE; } else { return drush_get_option($option, FALSE); } } drush-5.10.0/commands/core/scratch.php000066400000000000000000000013261222105546100176130ustar00rootroot00000000000000 'Show how many items remain to be indexed out of the total.', 'drupal dependencies' => array('search'), 'options' => array( 'pipe' => 'Display in the format remaining/total for processing by scripts.', ), ); $items['search-index'] = array( 'description' => 'Index the remaining search items without wiping the index.', 'drupal dependencies' => array('search'), ); $items['search-reindex'] = array( 'description' => 'Force the search index to be rebuilt.', 'drupal dependencies' => array('search'), 'options' => array( 'immediate' => 'Rebuild the index immediately, instead of waiting for cron.', ), ); return $items; } function drush_search_status() { list($remaining, $total) = _drush_search_status(); drush_print(dt('There are @remaining items out of @total still to be indexed.', array( '@remaining' => $remaining, '@total' => $total, ))); drush_print_pipe("$remaining/$total\n"); } function _drush_search_status() { $remaining = 0; $total = 0; if (drush_drupal_major_version() >= 7) { foreach (variable_get('search_active_modules', array('node', 'user')) as $module) { $status = module_invoke($module, 'search_status'); $remaining += $status['remaining']; $total += $status['total']; } } else { foreach (module_implements('search') as $module) { // Special case. Apachesolr recommends disabling core indexing with // search_cron_limit = 0. Need to avoid infinite status loop. if ($module == 'node' && variable_get('search_cron_limit', 10) == 0) { continue; } $status = module_invoke($module, 'search', 'status'); if (isset($status['remaining']) && isset($status['total'])) { $remaining += $status['remaining']; $total += $status['total']; } } } return array($remaining, $total); } function drush_search_index() { drush_op('_drush_search_index'); drush_log(dt('The search index has been built.'), 'ok'); } function _drush_search_index() { list($remaining, ) = _drush_search_status(); register_shutdown_function('search_update_totals'); $failures = 0; while ($remaining > 0) { drush_log(dt('Remaining items to be indexed: ' . $remaining), 'ok'); // Use drush_invoke_process() to start subshell. Avoids out of memory issue. $eval = "register_shutdown_function('search_update_totals');"; if (drush_drupal_major_version() >= 7) { // If needed, prod module_implements() to recognize our // hook_node_update_index() implementations. $implementations = module_implements('node_update_index'); if (!in_array('system', $implementations)) { // Note that this resets module_implements cache. module_implements('node_update_index', FALSE, TRUE); } foreach (variable_get('search_active_modules', array('node', 'user')) as $module) { $eval .= " module_invoke('$module', 'update_index');"; } } else { // If needed, prod module_implements() to recognize our hook_nodeapi() // implementations. $implementations = module_implements('nodeapi'); if (!in_array('system', $implementations)) { // Note that this resets module_implements cache. module_implements('nodeapi', FALSE, TRUE); } $eval .= " module_invoke_all('update_index');"; } drush_invoke_process('@self', 'php-eval', array($eval)); $previous_remaining = $remaining; list($remaining, ) = _drush_search_status(); // Make sure we're actually making progress. if ($remaining == $previous_remaining) { $failures++; if ($failures == 3) { drush_log(dt('Indexing stalled with @number items remaining.', array( '@number' => $remaining, )), 'error'); return; } } // Only count consecutive failures. else { $failures = 0; } } } function drush_search_reindex() { drush_print(dt("The search index must be fully rebuilt before any new items can be indexed.")); if (drush_get_option('immediate')) { drush_print(dt("Rebuilding the index may take a long time.")); } if (!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } if (drush_drupal_major_version() >= 7) { drush_op('search_reindex'); } else { drush_op('search_wipe'); } if (drush_get_option('immediate')) { drush_op('_drush_search_index'); drush_log(dt('The search index has been rebuilt.'), 'ok'); } else { drush_log(dt('The search index will be rebuilt.'), 'ok'); } } /** * Fake an implementation of hook_node_update_index() for Drupal 7. */ function system_node_update_index($node) { // Verbose output. if (drush_get_context('DRUSH_VERBOSE')) { drush_log(dt('Indexing node !nid.', array('!nid' => $node->nid)), 'ok'); } } /** * Fake an implementation of hook_nodeapi() for Drupal 6. */ function system_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { if ($op == 'update index') { // Verbose output. if (drush_get_context('DRUSH_VERBOSE')) { drush_log(dt('Indexing node !nid.', array('!nid' => $node->nid)), 'ok'); } } } drush-5.10.0/commands/core/shellalias.drush.inc000066400000000000000000000026011222105546100214100ustar00rootroot00000000000000 array_keys($all)); } } function shellalias_drush_command() { $items = array(); $items['shell-alias'] = array( 'description' => 'Print all known shell alias records.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'arguments' => array( 'alias' => 'Shell alias to print', ), 'aliases' => array('sha'), 'examples' => array( 'drush shell-alias' => 'List all alias records known to drush.', 'drush shell-alias pull' => 'Print the value of the shell alias \'pull\'.', ), ); return $items; } /** * Print out the specified shell aliases. */ function drush_core_shell_alias($alias = FALSE) { $shell_aliases = drush_get_context('shell-aliases', array()); if (!$alias) { drush_print_table(drush_key_value_to_array_table($shell_aliases)); } elseif (isset($shell_aliases[$alias])) { drush_print_table(drush_key_value_to_array_table(array($alias => $shell_aliases[$alias]))); } } drush-5.10.0/commands/core/site_install.drush.inc000066400000000000000000000132461222105546100217700ustar00rootroot00000000000000 $lower)), 'warning'); drush_set_option('sites-subdir', $lower); } // Make sure that we will bootstrap to the 'sites-subdir' site. drush_set_option('uri', 'http://' . $sites_subdir); } } /** * Perform setup tasks for installation. */ function drush_core_pre_site_install($profile = NULL) { if (!$db_spec = _drush_sql_get_db_spec()) { drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.')); return; } $default_sites_subdir = drush_get_context('DRUSH_DRUPAL_SITE', 'default'); $sites_subdir = drush_get_option('sites-subdir', $default_sites_subdir); $conf_path = "sites/$sites_subdir"; $files = "$conf_path/files"; $settingsfile = "$conf_path/settings.php"; if (!file_exists($files)) { $msg[] = dt('create a @files directory', array('@files' => $files)); } if (!file_exists($settingsfile)) { $msg[] = dt('create a @settingsfile file', array('@settingsfile' => $settingsfile)); } if (drush_sql_db_exists($db_spec)) { $msg[] = dt("DROP all tables in your '@db' database.", array('@db' => $db_spec['database'])); } else { $msg[] = dt("CREATE the '@db' database.", array('@db' => $db_spec['database'])); } if (!drush_confirm(dt('You are about to ') . implode(dt(' and '), $msg) . ' Do you want to continue?')) { return drush_user_abort(); } // Can't install without sites directory and settings.php. if (!file_exists($conf_path)) { if (!drush_mkdir($conf_path) && !drush_get_context('DRUSH_SIMULATE')) { drush_set_error(dt('Failed to create directory @conf_path', array('@conf_path' => $conf_path))); return; } } else { drush_log(dt('Sites directory @subdir already exists - proceeding.', array('@subdir' => $conf_path))); } if (!drush_file_not_empty($settingsfile)) { if (!drush_op('copy', 'sites/default/default.settings.php', $settingsfile) && !drush_get_context('DRUSH_SIMULATE')) { drush_set_error(dt('Failed to copy sites/default/default.settings.php to @settingsfile', array('@settingsfile' => $settingsfile))); return; } if (drush_drupal_major_version() == 6) { // On D6, we have to write $db_url ourselves. In D7+, the installer does it. file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_get_option('db-url') . "';\n", FILE_APPEND); // Instead of parsing and performing string replacement on the configuration file, // the options are appended and override the defaults. // Database table prefix if (!empty($db_spec['db_prefix'])) { if (is_array($db_spec['db_prefix'])) { // Write db_prefix configuration as an array $db_prefix_config = '$db_prefix = ' . var_export($db_spec['db_prefix'], TRUE) . ';'; } else { // Write db_prefix configuration as a string $db_prefix_config = '$db_prefix = \'' . $db_spec['db_prefix'] . '\';'; } file_put_contents($settingsfile, "\n" . $db_prefix_config . "\n", FILE_APPEND); } } } // If there is a profile, write it (as needed) into $conf['install_profile'] // to help drush find commandfiles earlier in the drush bootstrap. if (!empty($profile)) { $contents = file_get_contents($settingsfile); if (strpos($contents, "\$conf['install_profile']") === FALSE && is_writable($settingsfile)) { $install_profile_config = "\$conf['install_profile'] = '$profile';"; $appended = file_put_contents($settingsfile, "\n" . $install_profile_config . "\n", FILE_APPEND); } } // The Drupal 6 installer needs to bootstrap up to the specified site. // We need to be at least at DRUSH_BOOTSTRAP_DRUPAL_SITE to select the site uri to install to define('MAINTENANCE_MODE', 'install'); if (drush_drupal_major_version() == 6) { drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); } elseif ($sites_subdir != 'default') { drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE); } // Add a files dir if needed if (!file_exists($files)) { if (!drush_mkdir($files) && !drush_get_context('DRUSH_SIMULATE')) { drush_set_error(dt('Failed to create directory @name', array('@name' => $files))); return; } } // Empty or create the DB as needed. $db_name = $db_spec['database']; $scheme = _drush_sql_get_scheme($db_spec); $simulate = drush_get_context('DRUSH_SIMULATE'); if ($scheme === 'sqlite') { // With SQLite, we don't DROP DATABASEs. Each database is in a single file, // so we just remove the file. We also don't CREATE DATABASEs; it is created // when SQLite attempts to open a database file which doesn't exist. if (file_exists($db_spec['database']) && !$simulate) { if (!unlink($db_spec['database'])) { drush_set_error(dt('Could not drop database: @name', array('@name' => $db_name))); } } } else { drush_sql_empty_db($db_spec); } return TRUE; } /** * Command callback. */ function drush_core_site_install($profile = NULL) { $args = func_get_args(); $form_options = array(); if ($args) { // The first argument is the profile. $profile = array_shift($args); // Subsequent arguments are additional form values. foreach ($args as $arg) { list($key, $value) = explode('=', $arg); $form_options[$key] = $value; } } drush_include_engine('drupal', 'site_install', drush_drupal_major_version()); drush_core_site_install_version($profile, $form_options); } drush-5.10.0/commands/core/sitealias.drush.inc000066400000000000000000000333711222105546100212550ustar00rootroot00000000000000 'drush_sitealias_print', 'description' => 'Print site alias records for all known site aliases and local sites.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'arguments' => array( 'site' => 'Site specification to print', ), 'options' => array( 'table' => 'Display the alias name, root, uri and host in a table.', 'full' => 'Print the full alias record for each site. Default when aliases are specified on the command line.', 'component' => 'Print only the specified element from the full alias record.', 'short' => 'Print only the site alias name. Default when no command line arguments are specified.', 'pipe' => 'Print the long-form site specification for each site.', 'with-db' => 'Include the databases structure in the full alias record.', 'with-db-url' => 'Include the short-form db-url in the full alias record.', 'no-db' => 'Do not include the database record in the full alias record (default).', 'with-optional' => 'Include optional default items.', 'alias-name' => 'For a single alias, set the name to use in the output.', 'local' => 'Only display sites that are available on the local system (remote-site not set, and Drupal root exists).', ), 'aliases' => array('sa'), 'examples' => array( 'drush site-alias' => 'List all alias records known to drush.', 'drush site-alias @dev' => 'Print an alias record for the alias \'dev\'.', ), 'topics' => array('docs-aliases'), ); $items['site-set'] = array( 'description' => 'Set a site alias to work on that will persist for the current session.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'arguments' => array( 'site' => 'Site specification to use, or "-" for previous site.', ), 'aliases' => array('use'), 'examples' => array( 'drush site-set @dev' => 'Set the current session to use the @dev alias.', 'drush site-set user@server/path/to/drupal#sitename' => 'Set the current session to use a site specification.', 'drush site-set -' => 'Go back to the previously-set site (like `cd -`).', ), ); $items['site-reset'] = array( 'description' => 'Reset a persistently set site.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'handle-remote-commands' => TRUE, ); return $items; } /** * Command argument complete callback. * * @return * Array of available site aliases. */ function sitealias_sitealias_print_complete() { $site_specs = array_keys(_drush_sitealias_all_list()); ksort($site_specs); return array('values' => $site_specs); } /** * Return a list of all site aliases known to drush. * * The array key is the site alias name, and the array value * is the site specification for the given alias. */ function _drush_sitealias_alias_list() { return drush_get_context('site-aliases'); } /** * Return a list of all of the local sites at the current drupal root. * * The array key is the site folder name, and the array value * is the site specification for that site. */ function _drush_sitealias_site_list() { $site_list = array(); $base_path = drush_get_context('DRUSH_DRUPAL_ROOT'); if ($base_path) { $base_path .= '/sites'; $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all')); foreach ($files as $filename => $info) { if ($info->basename == 'settings.php') { $alias_record = drush_sitealias_build_record_from_settings_file($filename); if (!empty($alias_record)) { $site_list[drush_sitealias_uri_to_site_dir($alias_record['uri'])] = $alias_record; } } } } return $site_list; } /** * Return the list of all site aliases and all local sites. */ function _drush_sitealias_all_list() { drush_sitealias_load_all(); return array_merge(_drush_sitealias_alias_list(), _drush_sitealias_site_list()); } /** * Return the list of sites (aliases or local) that the * user specified on the command line. If none were specified, * then all are returned. */ function _drush_sitealias_user_specified_list() { $command = drush_get_command(); $specifications = $command['arguments']; $site_list = array(); // Did the user specify --short or --full output? $specified_output_style = drush_get_option(array('full', 'short'), FALSE); // Iterate over the arguments and convert them to alias records if (!empty($specifications)) { $site_list = drush_sitealias_resolve_sitespecs($specifications); if (!$specified_output_style) { drush_set_option('full', TRUE); } } // If the user provided no args, then we will return everything. else { $site_list = _drush_sitealias_all_list(); // Filter out the hidden items foreach ($site_list as $site_name => $one_site) { if (array_key_exists('#hidden', $one_site)) { unset($site_list[$site_name]); } } } // Filter out the local sites if (drush_get_option('local', FALSE)) { foreach ($site_list as $site_name => $one_site) { if ( (array_key_exists('remote-site', $one_site)) || (!array_key_exists('root', $one_site)) || (!is_dir($one_site['root'])) ) { unset($site_list[$site_name]); } } } return $site_list; } /** * Print out the specified site aliases using the format * specified. */ function drush_sitealias_print() { // Call bootstrap max, unless the caller requested short output if (!drush_get_option('short', FALSE)) { drush_bootstrap_max(); } $site_list = _drush_sitealias_user_specified_list(); ksort($site_list); $table_output = drush_get_option('table'); $full_output = drush_get_option('full'); $long_output = drush_get_option('long'); $with_db = (drush_get_option('with-db') != null) || (drush_get_option('with-db-url') != null); $site_specs = array(); $rows = array(); foreach ($site_list as $site => $alias_record) { if (!array_key_exists('site-list', $alias_record)) { $site_specs[] = drush_sitealias_alias_record_to_spec($alias_record, $with_db); } if (isset($table_output)) { $alias_record += array('root' => '', 'uri' => '', 'remote-host' => ''); $row = array($site, $alias_record['root'], $alias_record['uri'], $alias_record['remote-host']); if (empty($rows)) { $header = array(dt('Name'), dt('Root'), dt('URI'), dt('Host')); $rows = array($header); } $rows[] = $row; } elseif (isset($full_output)) { $component = drush_get_option('component'); if ($component) { if (array_key_exists($component, $alias_record)) { drush_print($alias_record[$component]); } else { drush_set_error('DRUSH_NO_SUCH_ELEMENT', dt('The element @component was not found in the alias record for @site.', array('@component' => $component, '@site' => $site))); } } else { _drush_sitealias_print_record($alias_record, $site); } } else { drush_print($site); } } $site_specs = array_unique($site_specs); asort($site_specs); drush_print_pipe(array_unique($site_specs)); if (!empty($rows)) { drush_print_table($rows, TRUE); } } /** * Given a site alias name, print out a php-syntax * representation of it. * * @param alias_record * The name of the site alias to print */ function _drush_sitealias_print_record($alias_record, $site_alias = '') { $output_db = drush_get_option('with-db'); $output_db_url = drush_get_option('with-db-url'); $output_optional_items = drush_get_option('with-optional'); // Make sure that the default items have been added for all aliases _drush_sitealias_add_static_defaults($alias_record); // Include the optional items, if requested if ($output_optional_items) { _drush_sitealias_add_transient_defaults($alias_record); } drush_sitealias_resolve_path_references($alias_record); if (isset($output_db_url)) { drush_sitealias_add_db_url($alias_record); } if (isset($output_db_url) || isset($output_db)) { drush_sitealias_add_db_settings($alias_record); } // If the user specified --with-db-url, then leave the // 'db-url' entry in the alias record (unless it is not // set, in which case we will leave the 'databases' record instead). if (isset($output_db_url)) { if (isset($alias_record['db-url'])) { unset($alias_record['databases']); } } // If the user specified --with-db, then leave the // 'databases' entry in the alias record. else if (isset($output_db)) { unset($alias_record['db-url']); } // If neither --with-db nor --with-db-url were specified, // then remove both the 'db-url' and the 'databases' entries. else { unset($alias_record['db-url']); unset($alias_record['databases']); } // The alias name will be the same as the site alias name, // unless the user specified some other name on the command line. $alias_name = drush_get_option('alias-name'); if (!isset($alias_name)) { $alias_name = $site_alias; if (empty($alias_name) || is_numeric($alias_name)) { $alias_name = drush_sitealias_uri_to_site_dir($alias_record['uri']); } } // We don't want the name or group to go into the output unset($alias_record['#name']); unset($alias_record['#group']); unset($alias_record['#hidden']); // We only want to output the 'root' item; don't output the '%root' path alias if (array_key_exists('path-aliases', $alias_record) && array_key_exists('%root', $alias_record['path-aliases'])) { unset($alias_record['path-aliases']['%root']); // If there is nothing left in path-aliases, then clear it out if (count($alias_record['path-aliases']) == 0) { unset($alias_record['path-aliases']); } } // Alias names contain an '@' when referenced, but do // not contain an '@' when defined. if (substr($alias_name,0,1) == '@') { $alias_name = substr($alias_name,1); } if (!drush_get_option('show-passwords', FALSE)) { drush_unset_recursive($alias_record, 'password'); } $exported_alias = var_export($alias_record, TRUE); drush_print('$aliases[\'' . $alias_name . '\'] = ' . $exported_alias . ';'); } /** * Use heuristics to attempt to convert from a site directory to a URI. * This function should only be used when the URI really is unknown, as * the mapping is not perfect. * * @param site_dir * A directory, such as domain.com.8080.drupal * * @return string * A uri, such as http://domain.com:8080/drupal */ function _drush_sitealias_site_dir_to_uri($site_dir) { // Protect IP addresses NN.NN.NN.NN by converting them // temporarily to NN_NN_NN_NN for now. $uri = preg_replace("/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/", "$1_$2_$3_$4", $site_dir); // Convert .[0-9]+. into :[0-9]+/ $uri = preg_replace("/\.([0-9]+)\./", ":$1/", $uri); // Convert .[0-9]$ into :[0-9] $uri = preg_replace("/\.([0-9]+)$/", ":$1", $uri); // Convert .(com|net|org|info). into .(com|net|org|info)/ $uri = str_replace(array('.com.', '.net.', '.org.', '.info.'), array('.com/', '.net/', '.org/', '.info/'), $uri); // If there is a / then convert every . after the / to / // Then again, if we did this we would break if the path contained a "." // I hope that the path would never contain a "."... $pos = strpos($uri, '/'); if ($pos !== false) { $uri = substr($uri, 0, $pos + 1) . str_replace('.', '/', substr($uri, $pos + 1)); } // n.b. this heuristic works all the time if there is a port, // it also works all the time if there is a port and no path, // but it does not work for domains such as .co.jp with no path, // and it can fail horribly if someone makes a domain like "info.org". // Still, I think this is the best we can do short of consulting DNS. // Convert from NN_NN_NN_NN back to NN.NN.NN.NN $uri = preg_replace("/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)/", "$1.$2.$3.$4", $site_dir); return 'http://' . $uri; } /** * Validation callback for drush site-set. */ function drush_sitealias_site_set_validate() { if (!function_exists('posix_getppid')) { $args = array('!command' => 'site-set', '!dependencies' => 'POSIX'); return drush_set_error('DRUSH_COMMAND_PHP_DEPENDENCY_ERROR', dt('Command !command needs the following PHP extensions installed/enabled to run: !dependencies.', $args)); } } /** * Set the DRUPAL_SITE variable by writing it out to a temporary file that we * then source for persistent site switching. * * @param site * A valid site specification. */ function drush_sitealias_site_set($site = '@none') { if ($filename = drush_sitealias_get_envar_filename()) { $last_site_filename = drush_sitealias_get_envar_filename('drush-drupal-prev-site-'); if ($site == '-') { if (file_exists($last_site_filename)) { $site = file_get_contents($last_site_filename); } else { $site = '@none'; } } if (_drush_sitealias_set_context_by_name($site)) { if (file_exists($filename)) { @unlink($last_site_filename); @rename($filename, $last_site_filename); } if ($site == '@none') { drush_delete_dir($filename); } else { file_put_contents($filename, $site); } drush_print(dt("Site set to !site", array('!site' => $site))); } else { drush_set_error('DRUPAL_SITE_NOT_FOUND', dt("Could not find a site definition for !site.", array('!site' => $site))); } } } /** * Deletes the file that stores the DRUPAL_SITE variable for persistent * site switching. */ function drush_sitealias_site_reset() { if (($filename = drush_sitealias_get_envar_filename()) && file_exists($filename)) { drush_delete_dir($filename); } } drush-5.10.0/commands/core/ssh.drush.inc000066400000000000000000000056331222105546100200740ustar00rootroot00000000000000 'Connect to a Drupal site\'s server via SSH for an interactive session or to run a shell command', 'arguments' => array( 'site-alias' => 'A remote site alias. Can be an alias list.', 'bash' => 'Bash to execute on target. Optional, except when site-alias is a list.', ), 'options' => drush_shell_exec_proc_build_options(), 'handle-remote-commands' => TRUE, 'strict-option-handling' => TRUE, 'examples' => array( 'drush @mysite ssh' => 'Open an interactive shell on @mysite\'s server.', 'drush @prod ssh \'ls /tmp\'' => 'Run "ls /tmp" on @prod site. If @prod is a site list, then ls will be executed on each site.', ), 'aliases' => array('ssh'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'topics' => array('docs-aliases'), ); return $items; } /** * Command callback. */ function drush_ssh_site_ssh($command = NULL) { // Get all of the args and options that appear after the command name. $args = drush_get_original_cli_args_and_options(); // n.b. we do not escape the first (0th) arg to allow `drush ssh 'ls /path'` // to work in addition to the preferred form of `drush ssh ls /path`. // Supporting the legacy form means that we cannot give the full path to an // executable if it contains spaces. for ($x = 1; $x < sizeof($args); $x++) { $args[$x] = drush_escapeshellarg($args[$x]); } $command = implode(' ', $args); if (!$alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { return drush_set_error('DRUSH_MISSING_TARGET_ALIAS', 'A remote site alias is required. The way you call ssh command has changed to `drush @alias ssh`.'); } $site = drush_sitealias_get_record($alias); // We only accept remote aliases. if (empty($site['remote-host'])) { drush_set_error('DRUSH_SITE_SSH_REMOTE_ALIAS_REQUIRED', dt('@alias does not specify a remote-host.', array('@alias' => $alias))); return; } // If we have multiple sites, run ourselves on each one. Set context back when done. if (isset($site['site-list'])) { if (empty($command)) { drush_set_error('DRUSH_SITE_SSH_COMMAND_REQUIRED', dt('A command is required when multiple site aliases are specified.')); return; } foreach ($site['site-list'] as $alias_single) { drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias_single); drush_ssh_site_ssh($command); } drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias); return; } $cmd = drush_shell_proc_build($site, $command); $status = drush_shell_proc_open($cmd); if ($status != 0) { return drush_set_error('DRUSH_SITE_SSH_ERROR', dt('An error @code occurred while running the command `@command`', array('@command' => $cmd, '@code' => $status))); } } drush-5.10.0/commands/core/test.drush.inc000066400000000000000000000245151222105546100202560ustar00rootroot00000000000000 "Run tests. Note that you must use the --uri option.", 'arguments' => array( 'targets' => 'A test class, a test group. If omitted, a list of test classes and test groups is presented. Delimit multiple targets using commas.', ), 'examples' => array( 'test-run' => 'List all available classes and groups.', 'sudo -u apache test-run --all' => 'Run all available tests. Avoid permission related failures by running as web server user.', 'test-run XMLRPCBasicTestCase' => 'Run one test class.', 'test-run XML-RPC' => 'Run all classes in a XML-RPC group.', 'test-run XML-RPC,Filter' => 'Run all tests from multiple groups/classes.', 'test-run XMLRPCBasicTestCase --methods="testListMethods, testInvalidMessageParsing"' => 'Run particular methods in the specified class or group.', ), 'options' => array( 'all' => 'Run all available tests', 'methods' => 'A comma delimited list of methods that should be run within the test class. Defaults to all methods.', 'dirty' => 'Skip cleanup of temporary tables and files. Helpful for reading debug() messages and other post-mortem forensics.', 'xml' => 'Output verbose test results to a specified directory using the JUnit test reporting format. Useful for integrating with Jenkins.' ), 'drupal dependencies' => array('simpletest'), // If you DRUSH_BOOTSTRAP_DRUPAL_LOGIN, you fall victim to http://drupal.org/node/974768. We'd like // to not bootstrap at all but simpletest uses Drupal to discover test classes, // cache the lists of tests, file_prepare_directory(), variable lookup like // httpauth creds, copy pre-built registry table from testing side, etc. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, ); $items['test-clean'] = array( 'description' => "Clean temporary tables and files.", 'drupal dependencies' => array('simpletest'), ); return $items; } /** * Command argument complete callback. * * @return * Array of test classes and groups. */ function test_test_run_complete() { if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL) == DRUSH_BOOTSTRAP_DRUPAL_FULL && module_exists('simpletest')) { // Retrieve all tests and groups. list($groups, $all_tests) = drush_test_get_all_tests(); return array('values' => array_keys($groups) + $all_tests); } } // Command callback function drush_test_clean() { return simpletest_clean_environment(); } // Validate hook function drush_test_run_validate($specs = NULL) { if (!drush_get_option('uri')) { // No longer needed? // return drush_set_error(dt("You must specify this site's URL using the --uri parameter.")); } } /** * Test-run command callback. * * @specs * A comma delimited string of test classes or group names. */ function drush_test_run($specs = NULL) { if (drush_drupal_major_version() > 7) { cache()->delete('simpletest'); } else { cache_clear_all('simpletest', 'cache'); } // Retrieve all tests and groups. list($groups, $all_tests) = drush_test_get_all_tests(); if (drush_get_option('all')) { // Run all tests. foreach (array_keys($groups) as $group) { foreach (array_keys($groups[$group]) as $class) { drush_invoke_process('@self', 'test-run', array($class)); } } return; } elseif (empty($specs)) { // No argument so list all groups/classes. return drush_test_list($groups); } elseif (strpos($specs, ',') === FALSE && in_array($specs, $all_tests)) { // We got one, valid class. Good, now run it. simpletest_drush_run_test($specs); if (!drush_get_option('dirty')) { simpletest_clean_environment(); } return; } // See if we got a list of specs. foreach (explode(',', $specs) as $spec) { $spec = trim($spec); if (in_array($spec, $all_tests)) { // Specific test class specified. drush_test_run_class($spec); } if (isset($groups[$spec])) { // Specific group specified. foreach (array_keys($groups[$spec]) as $class) { drush_test_run_class($class); } } } } /* * Run a test class via drush_invoke_process() * @param string $class * The test class to run. * * @return * If the command could not be completed successfully, FALSE. * If the command was completed, this will return an associative * array containing the results of the API call. * @see drush_backend_get_result() */ function drush_test_run_class($class) { $backend_options = array('output-label' => "$class ", 'integrate' => TRUE, 'output' => TRUE); $return = drush_invoke_process('@self', 'test-run', array($class), drush_redispatch_get_options(), $backend_options); return $return; } /** * Run a single test and display any failure messages. */ function simpletest_drush_run_test($class) { if (drush_drupal_major_version() >= 7) { $test_id = db_insert('simpletest_test_id') ->useDefaults(array('test_id')) ->execute(); } else { db_query('INSERT INTO {simpletest_test_id} (test_id) VALUES (default)'); $test_id = db_last_insert_id('simpletest_test_id', 'test_id'); } $test = new $class($test_id); if ($methods_string = drush_get_option('methods')) { foreach (explode(',', $methods_string) as $method) { $methods[] = trim($method); } $test->run($methods); } else { $test->run(); } $info = $test->getInfo(); $status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0) || (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'error' : 'ok'); drush_log($info['name'] . ' ' . _simpletest_format_summary_line($test->results), $status); if ($dir = drush_get_option('xml')) { drush_test_xml_results($test_id, $dir); } // If there were some failed tests show them. if ($status === 'error') { if (drush_drupal_major_version() >= 7) { $args = array(':test_id' => $test_id); $result = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $args); foreach($result as $record) { drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message", array('!function' => $record->function, '!message' => $record->message))); } } else { $result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $test_id); while ($row = db_fetch_object($result)) { drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message", array('!function' => $row->function, '!message' => $row->message))); } } } } /** * Retrieve all test groups and sanitize their names to make them command-line * friendly. */ function simpletest_drush_test_groups($tests) { $groups = array(); foreach (simpletest_categorize_tests($tests) as $name => $group) { $sanitized = strtr($name, array(' ' => '')); $groups[$sanitized] = $group; } return $groups; } // Print a listing of all available tests function drush_test_list($groups) { $rows[] = array(dt('Command'), dt('Description')); $rows[] = array('-------', '-----------'); foreach ($groups as $group_name => $group_tests) { foreach ($group_tests as $test_class => $test_info) { if (!isset($rows[$test_info['group']])) { $rows[$test_info['group']] = array($group_name, $test_info['group']); } $rows[] = array(" {$test_class}", " {$test_info['name']}"); } } return drush_print_table($rows, TRUE); } function drush_test_get_all_tests() { if (function_exists('simpletest_get_all_tests')) { $all_tests = simpletest_get_all_tests(); $groups = simpletest_drush_test_groups($all_tests); } else { $groups = simpletest_test_get_all(); $all_tests = array(); foreach ($groups as $group) { $all_tests = array_merge($all_tests, array_keys($group)); } } return array($groups, $all_tests); } /* * Display test results. */ function drush_test_xml_results($test_id, $dir) { $dir = is_string($dir) ? $dir : '.'; $results_map = array( 'pass' => 'Pass', 'fail' => 'Fail', 'exception' => 'Exception', ); if (drush_drupal_major_version() >= 7) { $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id)); } else { $result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id); $results = array(); while ($row = db_fetch_object($result)) { $results[] = $row; } } $test_class = ''; $xml_files = array(); foreach ($results as $result) { if (isset($results_map[$result->status])) { if ($result->test_class != $test_class) { // Display test class every time results are for new test class. if (isset($xml_files[$test_class])) { file_put_contents($dir . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML()); unset($xml_files[$test_class]); } $test_class = $result->test_class; if (!isset($xml_files[$test_class])) { $doc = new DomDocument('1.0'); $root = $doc->createElement('testsuite'); $root = $doc->appendChild($root); $xml_files[$test_class] = array('doc' => $doc, 'suite' => $root); } } // Save the result into the XML: $case = $xml_files[$test_class]['doc']->createElement('testcase'); $case->setAttribute('classname', $test_class); list($class, $name) = explode('->', $result->function, 2); $case->setAttribute('name', $name); if ($result->status == 'fail') { $fail = $xml_files[$test_class]['doc']->createElement('failure'); $fail->setAttribute('type', 'failure'); $fail->setAttribute('message', $result->message_group); $text = $xml_files[$test_class]['doc']->createTextNode($result->message); $fail->appendChild($text); $case->appendChild($fail); } $xml_files[$test_class]['suite']->appendChild($case); } } // Save the last one: if (isset($xml_files[$test_class])) { file_put_contents($dir . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML()); unset($xml_files[$test_class]); } } drush-5.10.0/commands/core/topic.drush.inc000066400000000000000000000051771222105546100204200ustar00rootroot00000000000000 'Read detailed documentation on a given topic.', 'arguments' => array( 'topic name' => 'The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).', ), 'examples' => array( 'drush topic' => 'Show all available topics.', 'drush topic docs-context' => 'Show documentation for the drush context API', 'drush docs-context' => 'Show documentation for the drush context API', ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'aliases' => array('topic'), 'topics' => array('docs-readme'), ); return $items; } /** * Implement hook_drush_help_alter(). Show 'Topics' section on help detail. */ function topic_drush_help_alter($command) { $implemented = drush_get_commands(); foreach ($command['topics'] as $topic_name) { // We have a related topic. Inject into the $command so the topic displays. $command['sections']['topic_section'] = 'Topics'; $command['topic_section'][$topic_name] = $implemented[$topic_name]['description']; } } /** * A command callback. * * Show a choice list of available topics and then dispatch to the respective command. * * @param string $topic_name * A command name. */ function drush_topic_core_topic($topic_name = NULL) { $commands = drush_get_commands(); if (is_null($topic_name)) { // Show choice list. foreach (drush_get_topics() as $key => $topic) { $choices[$key] = $topic['description']; } natcasesort($choices); if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)', array(5))) { return; } } // If the topic name is not found, check for // "docs-$topic_name". This allows users to be // just a bit lazy when selecting core topics by name. if (!isset($commands[$topic_name]) && isset($commands["docs-$topic_name"])) { $topic_name = "docs-$topic_name"; } return drush_dispatch($commands[$topic_name]); } /** * A command argument complete callback. * * @return * Available topic keys. */ function topic_core_topic_complete() { return array('values' => array_keys(drush_get_topics())); } /** * Retrieve all defined topics */ function drush_get_topics() { $commands = drush_get_commands(); foreach ($commands as $key => $command) { if (!empty($command['topic']) && empty($command['is_alias'])) { $topics[$key] = $command; } } return $topics; } drush-5.10.0/commands/core/usage.drush.inc000066400000000000000000000132751222105546100204040ustar00rootroot00000000000000 DRUSH_BOOTSTRAP_DRUSH, 'description' => 'Show Drush usage information that has been logged but not sent. ' . $disclaimer, 'examples' => array( 'drush usage-show' => 'Show cached usage statistics.', '$options[\'drush_usage_log\'] = TRUE;' => 'Specify in a .drushrc.php file that usage information should be logged locally in a usage statistics file.', ), 'aliases' => array('ushow'), ); $items['usage-send'] = array( 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'description' => 'Send anonymous Drush usage information to statistics logging site. ' . $disclaimer, 'examples' => array( 'drush usage-send' => 'Immediately send cached usage statistics.', '$options[\'drush_usage_send\'] = TRUE;' => 'Specify in a .drushrc.php file that usage information should be sent.', '$options[\'drush_usage_size\'] = 10240;' => 'Specify the frequency (file size) that usage information should be sent.', ), 'aliases' => array('usend'), ); return $items; } /* * Log and/or send usage data to Mongolab. * * An organization can implement own hook_drush_exit() to send data to a * different endpoint. */ function usage_drush_exit() { // Ignore statistics for simulated commands. (n.b. in simulated mode, _drush_usage_mongolab will print rather than send statistics) if (!drush_get_context('DRUSH_SIMULATE')) { $file = _drush_usage_get_file(); if (drush_get_option('drush_usage_log', FALSE)) { _drush_usage_log(drush_get_command(), $file); } if (drush_get_option('drush_usage_send', FALSE)) { _drush_usage_mongolab($file, drush_get_option('drush_usage_size', 51200)); } } } function drush_usage_send() { $file = _drush_usage_get_file(TRUE); if ($file) { drush_set_option('drush_usage_send', TRUE); drush_set_option('drush_usage_size', 0); drush_print(dt('To automatically send anonymous usage data, add the following to a .drushrc.php file: $options[\'drush_usage_send\'] = TRUE;')); } } function drush_usage_show() { $file = _drush_usage_get_file(TRUE); if ($file) { $json = '[' . file_get_contents($file) . ']'; $usage_data = json_decode($json); foreach ($usage_data as $item) { $cmd = $item->cmd; $options = (array) $item->opt; array_unshift($options, ''); drush_print($cmd . implode(' --', $options)); } } } function _drush_usage_get_file($required = FALSE) { $file = drush_directory_cache('usage') . '/usage.txt'; if (!file_exists($file) && $required) { return FALSE; // drush_set_error('DRUSH_NO_USAGE_FILE', dt("No usage file; set $options['drush_usage_log'] = TRUE; in a .drushrc.php file to enable.")); } return $file; } function _drush_usage_log($command, $file) { _drush_merge_engine_data($command); // Start out with just the options in the current command record. $options = _drush_get_command_options($command); // If 'allow-additional-options' contains a list of command names, // then union together all of the options from all of the commands. if (is_array($command['allow-additional-options'])) { $implemented = drush_get_commands(); foreach ($command['allow-additional-options'] as $subcommand_name) { if (array_key_exists($subcommand_name, $implemented)) { $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name])); } } } $used = drush_get_merged_options(); $command_specific = array_intersect(array_keys($used), array_keys($options)); $record = array( 'date' => $_SERVER['REQUEST_TIME'], 'cmd' => $command['command'], 'opt' => $command_specific, 'major' => DRUSH_MAJOR_VERSION, 'minor' => DRUSH_MINOR_VERSION, 'os' => php_uname('s'), 'host' => md5(php_uname('n') . get_current_user()), ); $prequel = (file_exists($file)) ? ",\n" : ""; if (file_put_contents($file, $prequel . json_encode($record), FILE_APPEND)) { drush_log(dt('Logged command and option names to local cache.'), 'debug'); } else { drush_log(dt('Failed to log command and option names to local cache.'), 'debug'); } } // We only send data periodically to save network traffic and delay. Files // are sent once they grow over 50KB (configurable). function _drush_usage_mongolab($file, $min_size_to_send) { $json = '[' . file_get_contents($file) . ']'; if (filesize($file) > $min_size_to_send) { $base = 'https://api.mongolab.com/api/1'; $apikey = '4eb95456e4b0bcd285d8135d'; // submitter account. $database = 'usage'; $collection = 'usage'; $action = "/databases/$database/collections/$collection"; $url = $base . $action . "?apiKey=$apikey"; $header = 'Content-Type: application/json'; if (!drush_shell_exec("wget -q -O - --no-check-certificate --timeout=20 --header=\"$header\" --post-data %s %s", $json, $url)) { if (!drush_shell_exec("curl -s --connect-timeout 20 --header \"$header\" --data %s %s", $json, $url)) { drush_log(dt('Drush usage statistics failed to post.'), 'debug'); return FALSE; } } drush_log(dt('Drush usage statistics successfully posted.'), 'debug'); // Empty the usage.txt file. unlink($file); return TRUE; } } drush-5.10.0/commands/core/variable.drush.inc000066400000000000000000000224201222105546100210550ustar00rootroot00000000000000 'Get a list of some or all site variables and values.', 'arguments' => array( 'name' => 'A string to filter the variables by. Variables whose name contains the string will be listed.', ), 'examples' => array( 'drush vget' => 'List all variables and values.', 'drush vget user' => 'List all variables containing the string "user".', 'drush vget site_mail --exact' => 'Show the variable with the exact key "site_mail".', ), 'options' => array( 'format' => array( 'description' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.', 'example-value' => 'export', ), 'pipe' => 'A synonym for --format=export. Useful for pasting into code.', 'exact' => 'Only get the one variable that exactly matches the specified name.', ), 'aliases' => array('vget'), ); $items['variable-set'] = array( 'description' => "Set a variable.", 'arguments' => array( 'name' => 'The name of a variable or the first few letters of its name.', 'value' => 'The value to assign to the variable. Use \'-\' to read the object from STDIN.', ), 'required-arguments' => TRUE, 'options' => array( 'yes' => 'Skip confirmation if only one variable name matches.', 'always-set' => array('description' => 'Older synonym for --exact; deprecated.', 'hidden' => TRUE), 'exact' => 'The exact name of the variable to set has been provided; do not prompt for similarly-named variables.', 'format' => array( 'description' => 'Format to parse the object. Use "auto" to detect format from value (default), "string", "integer" or "boolean" for corresponding primitive type, and "json" for JSON.', 'example-value' => 'boolean', ), ), 'examples' => array( 'drush vset --yes preprocess_css TRUE' => 'Set the preprocess_css variable to true. Skip confirmation if variable already exists.', 'drush vset --exact maintenance_mode 1' => 'Take the site offline; skips confirmation even if maintenance_mode variable does not exist. Variable is rewritten to site_offline for Drupal 6.', 'drush vset pr TRUE' => 'Choose from a list of variables beginning with "pr" to set to (bool)true.', 'php -r "print json_encode(array(\'drupal\', \'simpletest\'));" | drush vset --format=json project_dependency_excluded_dependencies -'=> 'Set a variable to a complex value (e.g. array)', ), 'aliases' => array('vset'), ); $items['variable-delete'] = array( 'description' => "Delete a variable.", 'arguments' => array( 'name' => 'The name of a variable or the first few letters of its name.', ), 'required-arguments' => TRUE, 'options' => array( 'yes' => 'Skip confirmation if only one variable name matches.', 'exact' => 'Only delete the one variable that exactly matches the specified name.', ), 'examples' => array( 'drush vdel user_pictures' => 'Delete the user_pictures variable.', 'drush vdel u' => 'Choose from a list of variables beginning with "u" to delete.', 'drush vdel -y --exact maintenance_mode' => 'Bring the site back online, skipping confirmation. Variable is rewritten to site_offline for Drupal 6.', ), 'aliases' => array('vdel'), ); return $items; } /** * Command argument complete callback. */ function variable_variable_get_complete() { return variable_complete_variables(); } /** * Command argument complete callback. */ function variable_variable_set_complete() { return variable_complete_variables(); } /** * Command argument complete callback. */ function variable_variable_delete_complete() { return variable_complete_variables(); } /** * List variables for completion. * * @return * Array of available variables. */ function variable_complete_variables() { if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { global $conf; return array('values' => array_keys($conf)); } } /** * Command callback. * List your site's variables. */ function drush_variable_get() { global $conf; $output = NULL; $keys = array_keys($conf); if ($args = func_get_args()) { $args[0] = drush_variable_name_adjust($args[0]); if (drush_get_option('exact', FALSE)) { $keys = in_array($args[0], $keys) ? array($args[0]) : array(); } $keys = preg_grep("/{$args[0]}/", $keys); } $pipe = array(); foreach ($keys as $name) { $value = $conf[$name]; $pipe[] = drush_format($value, $name, 'export'); drush_print(drush_format($value, $name)); $returns[$name] = $value; } drush_print_pipe($pipe); if (empty($keys)) { return drush_set_error('No matching variable found.'); } else { return $returns; } } /** * Command callback. * Set a variable. */ function drush_variable_set() { $args = func_get_args(); $value = $args[1]; if (!isset($value)) { return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.')); } $args[0] = drush_variable_name_adjust($args[0]); $result = drush_variable_like($args[0]); $options[] = "$args[0] ". dt('(new variable)'); $match = FALSE; while (!$match && $name = drush_db_result($result)) { if ($name == $args[0]) { $options[0] = $name; $match = TRUE; } else { $options[] = $name; } } if ($value == '-') { $value = stream_get_contents(STDIN); } // If the value is a string (usual case, unless we are called from code), // then format the input if (is_string($value)) { $value = _drush_variable_format($value, drush_get_option('format', 'auto')); } // Format the output for display if (is_array($value)) { $display = "\n" . var_export($value, TRUE); } elseif (is_integer($value)) { $display = $value; } elseif (is_bool($value)) { $display = $value ? "TRUE" : "FALSE"; } else { $display = '"' . $value . '"'; } // Check 'always-set' for compatibility with older scripts; --exact is preferred. $always_set = drush_get_option('always-set', FALSE) || drush_get_option('exact', FALSE); if ($always_set || count($options) == 1 || $match) { variable_set($args[0], $value); drush_log(dt('!name was set to !value.', array('!name' => $args[0], '!value' => $display)), 'success'); return ''; } else { $choice = drush_choice($options, 'Enter a number to choose which variable to set.'); if ($choice !== FALSE) { $choice = $options[$choice]; $choice = str_replace(' ' . dt('(new variable)'), '', $choice); drush_op('variable_set', $choice, $value); drush_log(dt('!name was set to !value', array('!name' => $choice, '!value' => $display)), 'success'); } } } function _drush_variable_format($value, $format) { if ($format == 'auto') { if (is_numeric($value)) { $format = 'integer'; } elseif (($value == 'TRUE') || ($value == 'FALSE')) { $format = 'bool'; } } // Now, we parse the object. switch ($format) { case 'integer': $value = (integer)$value; break; case 'bool': case 'boolean': if ($value == 'TRUE') { $value = TRUE; } elseif ($value == 'FALSE') { $value = FALSE; } else { $value = (bool)$value; } break; case 'json': $value = drush_json_decode($value); break; } return $value; } /** * Command callback. * Delete a variable. */ function drush_variable_delete() { $args = func_get_args(); $args[0] = drush_variable_name_adjust($args[0]); // Look for similar variable names. $result = drush_variable_like($args[0]); $options = array(); while ($name = drush_db_result($result)) { $options[] = $name; } if (drush_get_option('exact', FALSE)) { $options = in_array($args[0], $options) ? array($args[0]) : array(); } if (count($options) == 0) { drush_print(dt('!name not found.', array('!name' => $args[0]))); return ''; } if ((count($options) == 1) && drush_get_context('DRUSH_AFFIRMATIVE')) { drush_op('variable_del', $args[0]); drush_log(dt('!name was deleted.', array('!name' => $args[0])), 'success'); return ''; } else { $choice = drush_choice($options, 'Enter a number to choose which variable to delete.'); if ($choice !== FALSE) { $choice = $options[$choice]; drush_op('variable_del', $choice); drush_log(dt('!choice was deleted.', array('!choice' => $choice)), 'success'); } } } // Query for similar variable names. function drush_variable_like($arg) { return drush_db_select('variable', 'name', 'name LIKE :keyword', array(':keyword' => $arg . '%'), NULL, NULL, 'name'); } // Unify similar variable names across different versions of Drupal function drush_variable_name_adjust($arg) { if (($arg == 'maintenance_mode') && (drush_drupal_major_version() < 7)) { $arg = 'site_offline'; } if (($arg == 'site_offline') && (drush_drupal_major_version() >= 7)) { $arg = 'maintenance_mode'; } return $arg; } drush-5.10.0/commands/core/watchdog.drush.inc000066400000000000000000000323101222105546100210670ustar00rootroot00000000000000 'Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.', 'drupal dependencies' => array('dblog'), 'aliases' => array('wd-list'), ); $items['watchdog-show'] = array( 'description' => 'Show watchdog messages.', 'drupal dependencies' => array('dblog'), 'arguments' => array( 'wid' => 'Optional id of a watchdog message to show in detail. If not provided, a listing of most recent 10 messages will be displayed. Alternatively if a string is provided, watchdog messages will be filtered by it.', ), 'options' => array( 'count' => 'The number of messages to show. Defaults to 10.', 'severity' => 'Restrict to messages of a given severity level.', 'type' => 'Restrict to messages of a given type.', 'tail' => 'Continuously show new watchdog messages until interrupted.', 'sleep-delay' => 'To be used in conjunction with --tail. This is the number of seconds to wait between each poll to the database. Delay is 1 second by default.', 'full' => 'Return extended information about each message', ), 'examples' => array( 'drush watchdog-show' => 'Show a listing of most recent 10 messages.', 'drush watchdog-show 64' => 'Show in detail message with id 64.', 'drush watchdog-show "cron run succesful"' => 'Show a listing of most recent 10 messages containing the string "cron run succesful".', 'drush watchdog-show --count=46' => 'Show a listing of most recent 46 messages.', 'drush watchdog-show --severity=notice' => 'Show a listing of most recent 10 messages with a severity of notice.', 'drush watchdog-show --type=php' => 'Show a listing of most recent 10 messages of type php.', 'drush watchdog-show --tail --full' => 'Show a listing of most recent 10 messages with extended information about each one and continue showing messages as they are registered in the watchdog.', 'drush watchdog-show --tail --sleep-delay=2' => 'Do a tail of the watchdog with a delay of two seconds between each poll to the database.', ), 'aliases' => array('wd-show', 'ws'), ); $items['watchdog-delete'] = array( 'description' => 'Delete watchdog messages.', 'drupal dependencies' => array('dblog'), 'options' => array( 'severity' => 'Delete messages of a given severity level.', 'type' => 'Delete messages of a given type.', ), 'examples' => array( 'drush watchdog-delete all' => 'Delete all messages.', 'drush watchdog-delete 64' => 'Delete messages with id 64.', 'drush watchdog-delete "cron run succesful"' => 'Delete messages containing the string "cron run succesful".', 'drush watchdog-delete --severity=notice' => 'Delete all messages with a severity of notice.', 'drush watchdog-delete --type=cron' => 'Delete all messages of type cron.', ), 'aliases' => array('wd-del', 'wd-delete'), ); return $items; } /** * Command callback. */ function drush_core_watchdog_list() { $options['-- types --'] = dt('== message types =='); $types = core_watchdog_message_types(); foreach ($types as $type) { $options[] = $type; } $options['-- levels --'] = dt('== severity levels =='); drush_include_engine('drupal', 'environment'); $severities = core_watchdog_severity_levels(); foreach ($severities as $key => $value) { $options[] = "$value($key)"; } $option = drush_choice($options, dt('Select a message type or severity level.')); if ($option === FALSE) { return drush_user_abort(); } $ntypes = count($types); if ($option < $ntypes) { drush_set_option('type', $types[$option]); } else { drush_set_option('severity', $option - $ntypes); } drush_core_watchdog_show_many(); } /** * Command callback. */ function drush_core_watchdog_show($arg = NULL) { if (is_numeric($arg)) { drush_core_watchdog_show_one($arg); } else { drush_core_watchdog_show_many($arg); } } /** * Print a watchdog message. * * @param $wid * The id of the message to show. */ function drush_core_watchdog_show_one($wid) { $rsc = drush_db_select('watchdog', '*', 'wid = :wid', array(':wid' => $wid), 0, 1); $result = drush_db_fetch_object($rsc); if (!$result) { return drush_set_error(dt('Watchdog message #!wid not found.', array('!wid' => $wid))); } $result = core_watchdog_format_result($result, TRUE); foreach ($result as $key => $value) { $uc = ucfirst($key); $upper->$uc = $value; } drush_print_table(drush_key_value_to_array_table($upper)); print "\n"; } /** * Print a table of watchdog messages. * * @param $filter * String to filter the message's text by. */ function drush_core_watchdog_show_many($filter = NULL) { $count = drush_get_option('count', 10); $type = drush_get_option('type'); $severity = drush_get_option('severity'); $tail = drush_get_option('tail', FALSE); $full = drush_get_option('full', FALSE); $where = core_watchdog_query($type, $severity, $filter); if ($where === FALSE) { return drush_log(dt('Aborting.')); } $rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], 0, $count, 'wid', 'DESC'); if ($rsc === FALSE) { return drush_log(dt('Aborting.')); } $table = array(); $header = array(dt('Id'), dt('Date'), dt('Severity'), dt('Type'), dt('Message')); while ($result = drush_db_fetch_object($rsc)) { $row = core_watchdog_format_result($result, $full); $table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message); } if (empty($table) && !$tail) { return drush_log(dt('No log messages available.'), 'ok'); } else { drush_log(dt('Most recent !count watchdog log messages:', array('!count' => $count))); if ($tail) { $table = array_reverse($table); } array_unshift($table, $header); $tbl = drush_print_table($table, TRUE); } if ($tail) { // Reuse the table object to display each line generated while in tail mode. // To make it possible some hacking is done on the object: // remove the header and reset the rows on each iteration. $tbl->_headers = NULL; // Obtain the last wid. If the table has no rows, start at 0. if (count($table) > 1) { $last = array_pop($table); $last_wid = $last[0]; } else { $last_wid = 0; } // Adapt the where snippet. if ($where['where'] != '') { $where['where'] .= ' AND '; } $where['where'] .= 'wid > :wid'; // sleep-delay $sleep_delay = drush_get_option('sleep-delay', 1); while (TRUE) { $where['args'][':wid'] = $last_wid; $table = array(); // Reset table rows. $tbl->_data = array(); $rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], NULL, NULL, 'wid', 'ASC'); while ($result = drush_db_fetch_object($rsc)) { $row = core_watchdog_format_result($result, $full); $table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message); #$tbl->addRow(array($row->wid, $row->date, $row->severity, $row->type, $row->message)); $last_wid = $row->wid; } $tbl->addData($table); print $tbl->_buildTable(); sleep($sleep_delay); } } else { print "\n"; } } /** * Format a watchdog database row. * * @param $result * Array. A database result object. * @param $full * Boolean. Return extended message details. * @return * Array. The result object with some attributes themed. */ function core_watchdog_format_result($result, $full = FALSE) { // Severity. drush_include_engine('drupal', 'environment'); $severities = core_watchdog_severity_levels(); $result->severity = $severities[$result->severity]; // Date. $result->date = format_date($result->timestamp, 'custom', 'd/M H:i'); unset($result->timestamp); // Message. $variables = $result->variables; if (is_string($variables)) { $variables = unserialize($variables); } if (is_array($variables)) { $result->message = strtr($result->message, $variables); } unset($result->variables); $message_length = 188; // Print all the data available if ($full) { // Possible empty values. if (empty($result->link)) { unset($result->link); } if (empty($result->referer)) { unset($result->referer); } // Username. if ($account = user_load($result->uid)) { $result->username = $account->name; } else { $result->username = dt('Anonymous'); } unset($result->uid); $message_length = PHP_INT_MAX; } $result->message = truncate_utf8(strip_tags(decode_entities($result->message)), $message_length, FALSE, FALSE); return $result; } /** * Command callback. * * @param $arg * The id of the message to delete or 'all'. */ function drush_core_watchdog_delete($arg = NULL) { if ($arg == 'all') { drush_print(dt('All watchdog messages will be deleted.')); if (!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } drush_db_delete('watchdog'); drush_log(dt('All watchdog messages have been deleted.'), 'ok'); } else if (is_numeric($arg)) { drush_print(dt('Watchdog message #!wid will be deleted.', array('!wid' => $arg))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } $affected_rows = drush_db_delete('watchdog', 'wid=:wid', array(':wid' => $arg)); if ($affected_rows == 1) { drush_log(dt('Watchdog message #!wid has been deleted.', array('!wid' => $arg)), 'ok'); } else { return drush_set_error(dt('Watchdog message #!wid does not exist.', array('!wid' => $arg))); } } else { $type = drush_get_option('type'); $severity = drush_get_option('severity'); if ((is_null($arg))&&(is_null($type))&&(is_null($severity))) { return drush_set_error(dt('No options provided.')); } $where = core_watchdog_query($type, $severity, $arg, 'OR'); if ($where === FALSE) { // Drush set error was already called by core_watchdog_query return FALSE; } drush_print(dt('All messages with !where will be deleted.', array('!where' => preg_replace("/message LIKE %$arg%/", "message body containing '$arg'" , strtr($where['where'], $where['args']))))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } $affected_rows = drush_db_delete('watchdog', $where['where'], $where['args']); drush_log(dt('!affected_rows watchdog messages have been deleted.', array('!affected_rows' => $affected_rows)), 'ok'); } } /** * Build a WHERE snippet based on given parameters. * * @param $type * String. Valid watchdog type. * @param $severity * Int or String for a valid watchdog severity message. * @param $filter * String. Value to filter watchdog messages by. * @param $criteria * ('AND', 'OR'). Criteria for the WHERE snippet. * @return * False or array with structure ('where' => string, 'args' => array()) */ function core_watchdog_query($type = NULL, $severity = NULL, $filter = NULL, $criteria = 'AND') { $args = array(); $conditions = array(); if ($type) { $types = core_watchdog_message_types(); if (array_search($type, $types) === FALSE) { $msg = "Unrecognized message type: !type.\nRecognized types are: !types."; return drush_set_error('WATCHDOG_UNRECOGNIZED_TYPE', dt($msg, array('!type' => $type, '!types' => implode(', ', $types)))); } $conditions[] = "type = :type"; $args[':type'] = $type; } if (!is_null($severity)) { drush_include_engine('drupal', 'environment'); $severities = core_watchdog_severity_levels(); if (isset($severities[$severity])) { $level = $severity; } elseif (($key = array_search($severity, $severities)) !== FALSE) { $level = $key; } else { $level = FALSE; } if ($level === FALSE) { foreach ($severities as $key => $value) { $levels[] = "$value($key)"; } $msg = "Unknown severity level: !severity.\nValid severity levels are: !levels."; return drush_set_error(dt($msg, array('!severity' => $severity, '!levels' => implode(', ', $levels)))); } $conditions[] = 'severity = :severity'; $args[':severity'] = $level; } if ($filter) { $conditions[] = "message LIKE :filter"; $args[':filter'] = '%'.$filter.'%'; } $where = implode(" $criteria ", $conditions); return array('where' => $where, 'args' => $args); } /** * Helper function to obtain the message types based on drupal version. * * @return * Array of watchdog message types. */ function core_watchdog_message_types() { return _dblog_get_message_types(); } drush-5.10.0/commands/make/000077500000000000000000000000001222105546100154365ustar00rootroot00000000000000drush-5.10.0/commands/make/generate.make.inc000066400000000000000000000330701222105546100206420ustar00rootroot00000000000000 $file)), 'ok'); } else { drush_make_error('FILE_ERROR', dt("Unable to write .make file !file", array('!file' => $file))); } } /** * Create the $version_options array from the --include-versions and * --exclude-versions command line options. */ function _drush_make_generate_get_version_options() { // What projects should we pin the versions for? // Check the command-line options for details. foreach (array("include", "exclude") as $option) { $version_options[$option] = drush_get_option("$option-versions"); if ($version_options[$option] !== TRUE) { $version_options[$option] = array_filter(explode(",", $version_options[$option])); } } return $version_options; } /** * Generate the $projects makefile array for the current site. */ function _drush_make_generate_projects($all_extensions, $version_options) { $projects = array(); $project_libraries = array(); $system_requirements = system_requirements('runtime'); // Update xml expects the drupal version to be expressed as "7.x" or "8.x" // We used to check $system_requirements['drupal']['value'], but this now // contains values such as "7.10-dev". $drupal_major_version = drush_drupal_major_version() . '.x'; $core_project = strtolower($system_requirements['drupal']['title']); $projects[$core_project] = array('_type' => 'core'); if ($core_project != 'drupal') { $projects[$core_project]['custom_download'] = TRUE; $projects[$core_project]['type'] = 'core'; } else { // Drupal core - we can determine the version if required. if (_drush_generate_track_version("drupal", $version_options)) { $projects[$core_project]["version"] = drush_drupal_version(); } } // Non-default profiles section. $install_profile = variable_get('install_profile', ''); if (!in_array($install_profile, array('default', 'standard', 'minimal')) && $install_profile != '') { $projects[$install_profile]['type'] = $projects[$install_profile]['_type'] = 'profile'; $request = array( 'name' => $install_profile, 'drupal_version' => $drupal_major_version, ); if (!release_info_check_project($request, 'profile')) { $projects[$install_profile]['custom_download'] = TRUE; } } // Iterate installed projects to build $projects array. $extensions = $all_extensions; $project_info = drush_get_projects($extensions); foreach ($project_info as $name => $project) { // Discard the extensions within this project. At the end $extensions will // contain only extensions part of custom projects (not from drupal.org or // other update service). foreach ($project['extensions'] as $ext) { unset($extensions[$ext]); } if ($name == 'drupal') { continue; } $type = $project['type']; // Discard projects with all modules disabled. if (($type == 'module') && (!$project['status'])) { continue; } $projects[$name] = array('_type' => $type); // Check the project is on drupal.org or its own update service. $request = array( 'name' => $name, 'drupal_version' => $drupal_major_version, ); if (isset($project['status url'])) { $request['status url'] = $project['status url']; $projects[$name]['location'] = $status_url; } if (!release_info_check_project($request, $type)) { // It is not a project on drupal.org neither an external update service. $projects[$name]['type'] = $type; $projects[$name]['custom_download'] = TRUE; } // Add 'subdir' if the project is installed in a non-default location. if (isset($project['path'])) { $projects[$name] += _drush_generate_makefile_check_path($project); } // Add version number if this project's version is to be tracked. if (_drush_generate_track_version($name, $version_options) && $project["version"]) { $projects[$name]['version'] = preg_replace("/^" . DRUPAL_CORE_COMPATIBILITY . "-/", "", $project["version"]); } foreach ($project['extensions'] as $extension_name) { _drush_make_generate_add_patch_files($projects[$name], dirname($all_extensions[$extension_name]->filename)); } } // Add a project for each unknown extension. foreach ($extensions as $name => $extension) { list($project_name, $project_data) = _drush_generate_custom_project($name, $extension, $version_options); $projects[$project_name] = $project_data; } // Add libraries. if (function_exists('libraries_get_libraries')) { $libraries = libraries_get_libraries(); foreach ($libraries as $library_name => $library_path) { $path = explode('/', $library_path); $project_libraries[$library_name] = array( 'directory_name' => $path[(count($path) - 1)], 'custom_download' => TRUE, 'type' => 'library', '_type' => 'librarie', // For plural. ); } } return array($projects, $project_libraries); } /** * Record any patches that were applied to this project * per information stored in PATCHES.txt. */ function _drush_make_generate_add_patch_files(&$project, $location) { $patchfile = DRUPAL_ROOT . '/' . $location . '/PATCHES.txt'; if (is_file($patchfile)) { foreach (file($patchfile) as $line) { if (substr($line, 0, 2) == '- ') { $project['patches'][] = trim(substr($line, 2)); } } } } /** * Create a project record for an extension not downloaded from drupal.org */ function _drush_generate_custom_project($name, $extension, $version_options) { $project['_type'] = $extension->type; $project['type'] = $extension->type; $info_file = $extension->filename; $location = DRUPAL_ROOT . '/' . dirname($info_file); // To start off, we will presume that our custom extension is // stored in a folder named after its project, and there are // no subfolders between the .info file and the project root. $project_name = basename($location); drush_shell_cd_and_exec($location, 'git rev-parse --git-dir 2> ' . drush_bit_bucket()); $output = drush_shell_exec_output(); if (!empty($output)) { $git_dir = $output[0]; // Find the actual base of the git repository. $repo_root = $git_dir == ".git" ? $location : dirname($git_dir); // If the repository root is at the drupal root or some parent // of the drupal root, or some other location that could not // pausibly be a project, then there is nothing we can do. // (We can't tell Drush make to download some sub-part of a repo, // can we?) if ($repo_project_name = _drush_generate_validate_repo_location($repo_root)) { $project_name = $repo_project_name; drush_shell_cd_and_exec($repo_root, 'git remote show origin'); $output = drush_shell_exec_output(); foreach ($output as $line) { if (strpos($line, "Fetch URL:") !== FALSE) { $url = preg_replace('/ *Fetch URL: */', '', $line); if (!empty($url)) { // We use the unconventional-looking keys // `download][type` and `download][url` so that // we can produce output that appears to be two-dimensional // arrays from a single-dimensional array. $project['download][type'] = 'git'; $project['download][url'] = $url; // Fill in the branch as well. drush_shell_cd_and_exec($repo_root, 'git branch'); $output = drush_shell_exec_output(); foreach ($output as $line) { if ($line{0} == '*') { $branch = substr($line, 2); if ($branch != "master") { $project['download][branch'] = $branch; } } } // Put in the commit hash. drush_shell_cd_and_exec($repo_root, 'git log'); $output = drush_shell_exec_output(); if (substr($output[0], 0, 7) == "commit ") { $revision = substr($output[0], 7); if (_drush_generate_track_version($project_name, $version_options)) { $project['download][revision'] = $revision; } } // Add patch files, if any. _drush_make_generate_add_patch_files($project, $repo_root); } } } } } // If we could not figure out where the extension came from, then give up and // flag it as a "custom" download. if (!isset($project['download][type'])) { $project['custom_download'] = TRUE; } return array($project_name, $project); } /** * If the user has checked in the Drupal root, or the 'sites/all/modules' * folder into a git repository, then we do not want to confuse that location * with a "project". */ function _drush_generate_validate_repo_location($repo_root) { $project_name = basename($repo_root); // The Drupal root, or any folder immediately inside the Drupal // root cannot be a project location. if ((strlen(DRUPAL_ROOT) >= strlen($repo_root)) || (dirname($repo_root) == DRUPAL_ROOT)) { return NULL; } // Also exclude sites/* and sites/*/{modules,themes} and profile/* and // profile/*/{modules,themes}. return $project_name; } /** * Helper function to determine if a given project is to have its version * tracked. */ function _drush_generate_track_version($project, $version_options) { // A. If --exclude-versions has been specified: // A.a. if it's a boolean, check the --include-versions option. if ($version_options["exclude"] === TRUE) { // A.a.1 if --include-versions has been specified, ensure it's an array. if (is_array($version_options["include"])) { return in_array($project, $version_options["include"]); } // A.a.2 If no include array, then we're excluding versions for ALL // projects. return FALSE; } // A.b. if --exclude-versions is an array with items, check this project is in // it: if so, then return FALSE. elseif (is_array($version_options["exclude"]) && count($version_options["exclude"])) { return !in_array($project, $version_options["exclude"]); } // B. If by now no --exclude-versions, but --include-versions is an array, // examine it for this project. if (is_array($version_options["include"]) && count($version_options["include"])) { return in_array($project, $version_options["include"]); } // If none of the above conditions match, include version number by default. return TRUE; } /** * Helper function to check for a non-default installation location. */ function _drush_generate_makefile_check_path($project) { $info = array(); $type = $project['type']; $path = dirname($project['path']); // Check to see if the path is in a subdir sites/all/modules or // profiles/profilename/modules if (preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path) || preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path)) { $subdir = preg_replace(array('@^[a-zA-Z0-9_]*/[a-zA-Z0-9_]*/' . $type . 's/*@', "@/$name" . '$@'), '', $path); if (!empty($subdir)) { $info['subdir'] = $subdir; } } return $info; } /** * Generate the actual contents of the .make file. */ function _drush_make_generate_makefile_contents($projects, $libraries = array()) { $header = array(); $header[] = '; This file was auto-generated by drush make'; $header['core'] = DRUPAL_CORE_COMPATIBILITY; $header[] = ''; $header['api'] = MAKE_API; return _drush_make_generate_makefile_body($projects, $header) . _drush_make_generate_makefile_body($libraries); } function _drush_make_generate_makefile_body($projects, $output = array()) { $custom = FALSE; $previous_type = 'core'; foreach ($projects as $name => $project) { $type = (isset($project['type']) && ($project['type'] == 'library')) ? 'libraries' : 'projects'; if ($previous_type != $project['_type']) { $previous_type = $project['_type']; $output[] = '; ' . ucfirst($previous_type) . 's'; } unset($project['_type']); if (!$project && is_string($name)) { $output[] = $type . '[] = "' . $name . '"'; continue; } $base = $type . '[' . $name . ']'; if (isset($project['custom_download'])) { $custom = TRUE; $output[] = '; Please fill the following out. Type may be one of get, git, bzr or svn,'; $output[] = '; and url is the url of the download.'; $output[$base . '[download][type]'] = '""'; $output[$base . '[download][url]'] = '""'; unset($project['custom_download']); } foreach ($project as $key => $value) { if (is_array($value)) { foreach ($value as $item) { $output[$base . '[' . $key . '][]'] = '"' . $item . '"'; } } else { $output[$base . '[' . $key . ']'] = '"' . $value . '"'; } } $output[] = ''; } $string = ''; foreach ($output as $k => $v) { if (!is_numeric($k)) { $string .= $k . ' = ' . $v; } else { $string .= $v; } $string .= "\n"; } if ($custom) { drush_log(dt('Some of the properties in your makefile will have to be manually edited. Please do that now.'), 'warning'); } return $string; } drush-5.10.0/commands/make/make.download.inc000066400000000000000000000406021222105546100206560ustar00rootroot00000000000000 dirname($download_location), 'yes' => TRUE, 'package-handler' => 'wget', 'source' => $download['status url'], // This is only relevant for profiles, but we always want the variant to // be 'profile-only' so we don't end up with extra copies of core. 'variant' => $type == 'core' ? 'full' : 'profile-only', 'cache' => TRUE, ); if ($type == 'core') { $options['drupal-project-rename'] = basename($download_location); } if (drush_get_option('no-cache', FALSE)) { unset($options['cache']); } $backend_options = array(); if (!drush_get_option(array('verbose', 'debug'), FALSE)) { $backend_options['integrate'] = TRUE; $backend_options['log'] = FALSE; } // Perform actual download with `drush pm-download`. $return = drush_invoke_process('@none', 'pm-download', array($full_project_version), $options, $backend_options); if (empty($return['error_log'])) { // @todo Report the URL we used for download. See // http://drupal.org/node/1452672. drush_log(dt('@project downloaded.', array('@project' => $full_project_version)), 'ok'); } } /** * Downloads a file to the specified location. * * @return mixed * The destination directory on success, FALSE on failure. */ function make_download_file($name, $type, $download, $download_location, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) { if ($filename = _make_download_file($download['url'], $cache_duration)) { if (!drush_get_option('ignore-checksums') && !_make_verify_checksums($download, $filename)) { return FALSE; } drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), 'ok'); $download_filename = isset($download['filename']) ? $download['filename'] : ''; $subtree = isset($download['subtree']) ? $download['subtree'] : NULL; return make_download_file_unpack($filename, $download_location, $download_filename, $subtree); } make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url']))); return FALSE; } /** * Wrapper to drush_download_file(). * * @param string $download * The url of the file to download. * @param int $cache_duration * The time in seconds to cache the resultant download. * * @return string * The location of the downloaded file, or FALSE on failure. */ function _make_download_file($download, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) { if (drush_get_option('no-cache', FALSE)) { $cache_duration = 0; } $tmp_path = make_tmp(); // Ensure that we aren't including the querystring when generating a filename // to save our download to. $file = basename(current(explode('?', $download, 2))); return drush_download_file($download, $tmp_path . '/' . $file, $cache_duration); } /** * Unpacks a file to the specified download location. * * @return mixed * The download location on success, FALSE on failure. */ function make_download_file_unpack($filename, $download_location, $name, $subtree = NULL) { $success = FALSE; if (drush_file_is_tarball($filename)) { $tmp_location = drush_tempdir(); if (!drush_tarball_extract($filename, $tmp_location)) { return FALSE; } if ($subtree) { $tmp_location .= '/' . $subtree; if (!file_exists($tmp_location)) { return drush_set_error('DRUSH_MAKE_SUBTREE_NOT_FOUND', dt('Directory !subtree not found within !file', array('!subtree' => $subtree, '!file' => $filename))); } } else { $files = scandir($tmp_location); unset($files[0]); // . directory unset($files[1]); // .. directory if ((count($files) == 1) && is_dir($tmp_location . '/' . current($files))) { $tmp_location .= '/' . current($files); } } $success = drush_move_dir($tmp_location, $download_location, TRUE); // Remove the tarball. if (file_exists($filename)) { drush_delete_dir($filename, TRUE); } } else { // If this is an individual file, and no filename has been specified, // assume the original name. if (is_file($filename) && !$name) { $name = basename($filename); } // The destination directory has already been created by // findDownloadLocation(). $destination = $download_location . ($name ? '/' . $name : ''); $success = drush_move_dir($filename, $destination, TRUE); } return $success ? $download_location : FALSE; } /** * Move a downloaded and unpacked file or directory into place. * * TODO merge with core drush methods. */ function _make_download_file_move($tmp_path, $filename, $download_location, $subtree = NULL) { $lines = drush_scan_directory($tmp_path, '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE); $main_directory = basename($download_location); if (count($lines) == 1) { $directory = array_shift($lines); if ($directory->basename != $main_directory) { drush_move_dir($directory->filename, $tmp_path . DIRECTORY_SEPARATOR . $main_directory, TRUE); } drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . $main_directory . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE); drush_delete_dir($tmp_path, TRUE); } elseif (count($lines) > 1) { drush_delete_dir($download_location, TRUE); drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE); } // Remove the tarball. if (file_exists($filename)) { drush_delete_dir($filename, TRUE); } if (file_exists($tmp_path)) { drush_delete_dir($tmp_path, TRUE); } return TRUE; } /** * For backwards compatibility. */ function make_download_get($name, $type, $download, $download_location) { return make_download_file($name, $type, $download, $download_location); } /** * Checks out a git repository to the specified download location. * * Allowed parameters in $download, in order of precedence: * - 'tag' * - 'revision' * - 'branch' * * This will also attempt to write out release information to the * .info file if the 'no-gitinfofile' option is FALSE. If * $download['full_version'] is present, this will be used, otherwise, * version will be set in this order of precedence: * - 'tag' * - 'branch' * - 'revision' * * @return mixed * The download location on success, FALSE otherwise. */ function make_download_git($name, $type, $download, $download_location) { $tmp_path = make_tmp(); $wc = drush_get_option('working-copy'); // If no download URL specified, assume anonymous clone from git.drupal.org. $download['url'] = isset($download['url']) ? $download['url'] : "http://git.drupal.org/project/$name.git"; // If no working-copy download URL specified, assume it is the same. $download['wc_url'] = isset($download['wc_url']) ? $download['wc_url'] : $download['url']; // If not a working copy, and if --no-cache has not been explicitly // declared, create a new git reference cache of the remote repository, // or update the existing cache to fetch recent changes. // @see package_handler_download_project() $cache = !$wc && !drush_get_option('no-cache', FALSE); if ($cache && ($git_cache = drush_directory_cache('git'))) { $project_cache = $git_cache . '/' . $name . '-' . md5($download['url']); // Set up a new cache, if it doesn't exist. if (!file_exists($project_cache)) { $command = 'git clone --mirror'; if (drush_get_context('DRUSH_VERBOSE')) { $command .= ' --verbose --progress'; } $command .= ' %s %s'; drush_shell_cd_and_exec($git_cache, $command, $download['url'], $project_cache); } else { // Update the --mirror clone. drush_shell_cd_and_exec($project_cache, 'git remote update'); } $git_cache = $project_cache; } // Use working-copy download URL if --working-copy specified. $url = $wc ? $download['wc_url'] : $download['url']; $tmp_location = drush_tempdir() . '/' . basename($download_location); $command = 'git clone %s %s'; if (drush_get_context('DRUSH_VERBOSE')) { $command .= ' --verbose --progress'; } if ($cache) { $command .= ' --reference ' . drush_escapeshellarg($git_cache); } // Before we can checkout anything, we need to clone the repository. if (!drush_shell_exec($command, $url, $tmp_location)) { make_error('DOWNLOAD_ERROR', dt('Unable to clone @project from @url.', array('@project' => $name, '@url' => $url))); return FALSE; } drush_log(dt('@project cloned from @url.', array('@project' => $name, '@url' => $url)), 'ok'); // Get the current directory (so we can move back later). $cwd = getcwd(); // Change into the working copy of the cloned repo. chdir($tmp_location); // We want to use the most specific target possible, so first try a refspec. if (!empty($download['refspec'])) { if (drush_shell_exec("git fetch %s %s", $url, $download['refspec'])) { drush_log(dt("Fetched refspec !refspec.", array('!refspec' => $download['refspec'])), 'ok'); if (drush_shell_exec("git checkout FETCH_HEAD")) { drush_log(dt("Checked out FETCH_HEAD."), 'info'); } } else { make_error('DOWNLOAD_ERROR', dt("Unable to fetch the refspec @refspec from @project.", array('@refspec' => $download['refspec'], '@project' => $name))); } } // If there wasn't a refspec, try a tag. elseif (!empty($download['tag'])) { // @TODO: change checkout to refs path. if (drush_shell_exec("git checkout %s", 'refs/tags/' . $download['tag'])) { drush_log(dt("Checked out tag @tag.", array('@tag' => $download['tag'])), 'ok'); } else { make_error('DOWNLOAD_ERROR', dt("Unable to check out tag @tag.", array('@tag' => $download['tag']))); } } // If there wasn't a tag, try a specific revision hash. elseif (!empty($download['revision'])) { if (drush_shell_exec("git checkout %s", $download['revision'])) { drush_log(dt("Checked out revision @revision.", array('@revision' => $download['revision'])), 'ok'); } else { make_error('DOWNLOAD_ERROR', dt("Unable to checkout revision @revision", array('@revision' => $download['revision']))); } } // If not, see if we at least have a branch. elseif (!empty($download['branch'])) { if (drush_shell_exec("git checkout %s", $download['branch']) && (trim(implode(drush_shell_exec_output())) != '')) { drush_log(dt("Checked out branch @branch.", array('@branch' => $download['branch'])), 'ok'); } elseif (drush_shell_exec("git checkout -b %s %s", $download['branch'], 'origin/' . $download['branch'])) { drush_log(dt('Checked out branch origin/@branch.', array('@branch' => $download['branch'])), 'ok'); } else { make_error('DOWNLOAD_ERROR', dt('Unable to check out branch @branch.', array('@branch' => $download['branch']))); } } if (!empty($download['submodule'])) { $command = 'git submodule update'; foreach ($download['submodule'] as $option) { $command .= ' --%s'; } if (call_user_func_array('drush_shell_exec', array_merge(array($command), $download['submodule']))) { drush_log(dt('Initialized registered submodules.'), 'ok'); } else { make_error('DOWNLOAD_ERROR', dt('Unable to initialize submodules.')); } } // Move back to last current directory (first line). chdir($cwd); // Move the directory into the final resting location. drush_copy_dir($tmp_location, $download_location, TRUE); return dirname($tmp_location); } /** * Checks out a Bazaar repository to the specified download location. * * @return mixed * The download location on success, FALSE otherwise. */ function make_download_bzr($name, $type, $download, $download_location) { $tmp_path = make_tmp(); $tmp_location = drush_tempdir() . '/' . basename($download_location); if (!empty($download['url'])) { $args = array(); $command = 'bzr'; if (drush_get_option('working-copy')) { $command .= ' branch --use-existing-dir'; } else { $command .= ' export'; } if (isset($download['revision'])) { $command .= ' -r %s'; $args[] = $download['revision']; } $command .= ' %s %s'; if (drush_get_option('working-copy')) { $args[] = $download['url']; $args[] = $tmp_location; } else { $args[] = $tmp_location; $args[] = $download['url']; } array_unshift($args, $command); if (call_user_func_array('drush_shell_exec', $args)) { drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), 'ok'); drush_copy_dir($tmp_location, $download_location, TRUE); return dirname($download_location); } } else { $download['url'] = dt("unspecified location"); } make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url']))); drush_delete_dir(dirname($tmp_location), TRUE); return FALSE; } /** * Checks out an SVN repository to the specified download location. * * @return mixed * The download location on success, FALSE otherwise. */ function make_download_svn($name, $type, $download, $download_location) { if (!empty($download['url'])) { if (!empty($download['interactive'])) { $function = 'drush_shell_exec_interactive'; } else { $options = ' --non-interactive'; $function = 'drush_shell_exec'; } if (!isset($download['force']) || $download['force']) { $options = ' --force'; } if (drush_get_option('working-copy')) { $command = 'svn' . $options . ' checkout'; } else { $command = 'svn' . $options . ' export'; } $args = array(); if (isset($download['revision'])) { $command .= ' -r%s'; $args[] = $download['revision']; } $command .= ' %s %s'; $args[] = $download['url']; $args[] = $download_location; if (!empty($download['username'])) { $command .= ' --username %s'; $args[] = $download['username']; if (!empty($download['password'])) { $command .= ' --password %s'; $args[] = $download['password']; } } array_unshift($args, $command); $result = call_user_func_array($function, $args); if ($result) { $args = array( '@project' => $name, '@command' => $command, '@url' => $download['url'], ); drush_log(dt('@project @command from @url.', $args), 'ok'); return $download_location; } else { $download['url'] = dt("unspecified location"); } } else { make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url']))); return FALSE; } } /** * Test that any supplied hash values match the hash of the file content. * * Unsupported hash algorithms are reported as failure. */ function _make_verify_checksums($info, $filename) { $hash_algos = array('md5', 'sha1', 'sha256', 'sha512'); // We only have something to do if a key is an // available function. if (array_intersect(array_keys($info), $hash_algos)) { $content = file_get_contents($filename); foreach ($hash_algos as $algo) { if (!empty($info[$algo])) { $hash = _make_hash($algo, $content); if ($hash !== $info[$algo]) { $args = array( '@algo' => $algo, '@file' => basename($filename), '@expected' => $info[$algo], '@hash' => $hash, ); make_error('DOWNLOAD_ERROR', dt('Checksum @algo verification failed for @file. Expected @expected, received @hash.', $args)); return FALSE; } } } } return TRUE; } /** * Calculate the hash of a string for a given algorithm. */ function _make_hash($algo, $string) { switch ($algo) { case 'md5': return md5($string); case 'sha1': return sha1($string); default: return function_exists('hash') ? hash($algo, $string) : ''; } } drush-5.10.0/commands/make/make.drush.inc000066400000000000000000000517611222105546100202040ustar00rootroot00000000000000 DRUSH_BOOTSTRAP_DRUSH, 'description' => 'Turns a makefile into a working Drupal codebase.', 'arguments' => array( 'makefile' => 'Filename of the makefile to use for this build.', 'build path' => 'The path at which to build the makefile.', ), 'examples' => array( 'drush make example.make example' => 'Build the example.make makefile in the example directory.', 'drush make --no-core --contrib-destination=. installprofile.make' => 'Build an installation profile within an existing Drupal site', 'drush make http://example.com/example.make example' => 'Build the remote example.make makefile in the example directory.', ), 'options' => array( 'version' => 'Print the make API version and exit.', 'concurrency' => array( 'description' => 'Set the number of concurrent projects that will be processed at the same time. The default is 1.', 'example-value' => '1', ), 'contrib-destination' => 'Specify a path under which modules and themes should be placed. Defaults to sites/all.', 'force-complete' => 'Force a complete build even if errors occur.', 'ignore-checksums' => 'Ignore md5 checksums for downloads.', 'md5' => array( 'description' => 'Output an md5 hash of the current build after completion. Use --md5=print to print to stdout.', 'example-value' => 'print', 'value' => 'optional', ), 'make-update-default-url' => 'The default location to load the XML update information from.', 'no-cache' => 'Do not use the pm-download caching (defaults to cache enabled).', 'no-clean' => 'Leave temporary build directories in place instead of cleaning up after completion.', 'no-core' => 'Do not require a Drupal core project to be specified.', 'no-patch-txt' => 'Do not write a PATCHES.txt file in the directory of each patched project.', 'no-gitinfofile' => 'Do not modify .info files when cloning from Git.', 'prepare-install' => 'Prepare the built site for installation. Generate a properly permissioned settings.php and files directory.', 'tar' => 'Generate a tar archive of the build. The output filename will be [build path].tar.gz.', 'test' => 'Run a temporary test build and clean up.', 'translations' => 'Retrieve translations for the specified comma-separated list of language(s) if available for all projects.', 'working-copy' => 'Preserves VCS directories, like .git, for projects downloaded using such methods.', 'download-mechanism' => 'How to download files. Should be autodetected, but this is an override if it doesn\'t work. Options are "curl" and "make" (a native download method).', 'projects' => array( 'description' => 'Restrict the make to this comma-separated list of projects. To specify all projects, pass *.', 'example' => 'views,ctools', ), 'libraries' => array( 'description' => 'Restrict the make to this comma-separated list of libraries. To specify all libraries, pass *.', 'example' => 'tinymce', ), ), 'engines' => array('release_info'), 'topics' => array('docs-make', 'docs-make-example'), ); $items['make-generate'] = array( 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, 'description' => 'Generate a makefile from the current Drupal site.', 'examples' => array( 'drush generate-makefile example.make' => 'Generate a makefile with ALL projects versioned (should a project have a known version number)', 'drush generate-makefile example.make --exclude-versions' => 'Generate a makefile with NO projects versioned', 'drush generate-makefile example.make --exclude-versions=drupal,views,cck' => 'Generate a makefile with ALL projects versioned EXCEPT core, Views and CCK', 'drush generate-makefile example.make --include-versions=admin_menu,og,ctools (--exclude-versions)' => 'Generate a makefile with NO projects versioned EXCEPT Admin Menu, OG and CTools.', ), 'options' => array( 'exclude-versions' => 'Exclude all version numbers (default is include all version numbers) or optionally specify a list of projects to exclude from versioning', 'include-versions' => 'Include a specific list of projects, while all other projects remain unversioned in the makefile (so implies --exclude-versions)', ), 'engines' => array('release_info'), 'aliases' => array('generate-makefile'), ); // Hidden command to build a single project. $items['make-process'] = array( 'hidden' => TRUE, 'arguments' => array( 'directory' => 'The temporary working directory to use', ), 'options' => array( 'project' => 'The project array as generated by make_projects()', ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'engines' => array('release_info'), ); // Add docs topic. $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH); $items['docs-make'] = array( 'description' => 'Drush Make overview with examples', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/docs/make.txt'), ); $items['docs-make-example'] = array( 'description' => 'Drush Make example makefile', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array($docs_dir . '/examples/example.make'), ); return $items; } /** * Implements hook_drush_help(). */ function make_drush_help($section) { switch ($section) { case 'drush:make': return 'Turns a makefile into a Drupal codebase. For a full description of options and makefile syntax, see docs/make.txt and examples/example.make.'; case 'drush:make-generate': return 'Generate a makefile from the current Drupal site, specifying project version numbers unless not known or otherwise specified. Unversioned projects will be interpreted later by drush make as "most recent stable release"'; } } /** * Command argument complete callback. * * @return array * Strong glob of files to complete on. */ function make_make_complete() { return array( 'files' => array( 'directories' => array( 'pattern' => '*', 'flags' => GLOB_ONLYDIR, ), 'make' => array( 'pattern' => '*.make', ), ), ); } /** * Drush callback; make based on the makefile. */ function drush_make($makefile = NULL, $build_path = NULL) { // If --version option is supplied, print it and bail. if (drush_get_option('version', FALSE)) { drush_print(dt('drush make API version !version', array('!version' => MAKE_API))); drush_print_pipe(MAKE_API); return; } if (!($build_path = make_build_path($build_path))) { return FALSE; } $info = make_parse_info_file($makefile); // Support making just a portion of a make file. $include_only = array( 'projects' => array_filter(drush_get_option_list('projects')), 'libraries' => array_filter(drush_get_option_list('libraries')), ); $info = make_prune_info_file($info, $include_only); if ($info === FALSE || ($info = make_validate_info_file($info)) === FALSE) { return FALSE; } drush_log(dt('Beginning to build !makefile.', array('!makefile' => $makefile)), 'ok'); if (make_projects(FALSE, drush_get_option('contrib-destination', 'sites/all'), $info, $build_path)) { make_libraries(drush_get_option('contrib-destination', 'sites/all'), $info, $build_path); if (drush_get_option('prepare-install')) { make_prepare_install($build_path); } } return $info; } /** * Drush callback: hidden file to process an individual project. */ function drush_make_process($directory) { // Set the temporary directory. make_tmp(TRUE, $directory); $project = drush_get_option('project', FALSE); if ($instance = DrushMakeProject::getInstance($project['type'], $project)) { $instance->make(); } else { make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project', array('%type' => $project['type'], '%project' => $project['name']))); } } /** * Implements drush_hook_post_COMMAND() for the make command. */ function drush_make_post_make($makefile = NULL, $build_path = NULL) { if (drush_get_option('version')) { return; } if (!($build_path = make_build_path($build_path))) { return; } if ($option = drush_get_option('md5')) { $md5 = make_md5(); if ($option === 'print') { drush_print($md5); } else { drush_log(dt('Build hash: %md5', array('%md5' => $md5)), 'ok'); } } // Only take final build steps if not in testing mode. if (!drush_get_option('test')) { if (drush_get_option('tar')) { make_tar($build_path); } else { make_move_build($build_path); } } make_clean_tmp(); } /** * Process all projects specified in the make file. */ function make_projects($recursion, $contrib_destination, $info, $build_path) { $projects = array(); if (empty($info['projects'])) { if (drush_get_option('no-core') || $recursion) { return TRUE; } else { drush_set_error('MAKE_NO_CORE', dt('No core project specified.')); return FALSE; } } $ignore_checksums = drush_get_option('ignore-checksums'); $translations = array(); if (isset($info['translations'])) { $translations = $info['translations']; } if ($arg_translations = drush_get_option('translations', FALSE)) { $translations = array_merge(explode(',', $arg_translations), $translations); } foreach ($info['projects'] as $key => $project) { $md5 = ''; if (isset($project['md5'])) { $md5 = $project['md5']; } // Merge the known data onto the project info. $project += array( 'name' => $key, 'core' => $info['core'], 'translations' => $translations, 'build_path' => $build_path, 'contrib_destination' => $contrib_destination, 'version' => '', 'location' => drush_get_option('make-update-default-url', RELEASE_INFO_DEFAULT_URL), 'subdir' => '', 'directory_name' => '', ); // If download components are specified, but not the download // type, default to git. Additionally, if the 'revision' parameter is // passed at the top level, this is short-hand for download revision. if (isset($project['revision']) && !isset($project['download']['revision'])) { $project['download']['revision'] = $project['revision']; } if (isset($project['download']) && !isset($project['download']['type'])) { $project['download']['type'] = 'git'; } if (!isset($project['l10n_url']) && ($project['location'] == RELEASE_INFO_DEFAULT_URL)) { $project['l10n_url'] = MAKE_DEFAULT_L10N_SERVER; } // For convenience: define $request to be compatible with release_info // engine. // TODO: refactor to enforce 'make' to internally work with release_info // keys. $request = make_prepare_request($project); if ($project['location'] != RELEASE_INFO_DEFAULT_URL && !isset($project['type'])) { // Set the cache option based on our '--no-cache' option. $cache_before = drush_get_option('cache'); if (!drush_get_option('no-cache', FALSE)) { drush_set_option('cache', TRUE); } $project_type = release_info_check_project($request, 'core'); // Restore the previous '--cache' option value. drush_set_option('cache', $cache_before); $project['download_type'] = ($project_type ? 'core' : 'contrib'); } elseif (!empty($project['type'])) { $project['download_type'] = ($project['type'] == 'core' ? 'core' : 'contrib'); } else { $project['download_type'] = ($project['name'] == 'drupal' ? 'core' : 'contrib'); } $projects[$project['download_type']][$project['name']] = $project; } $cores = !empty($projects['core']) ? count($projects['core']) : 0; if (drush_get_option('no-core')) { unset($projects['core']); } elseif ($cores == 0 && !$recursion) { drush_set_error('MAKE_NO_CORE', dt('No core project specified.')); return FALSE; } elseif ($cores == 1 && $recursion) { unset($projects['core']); } elseif ($cores > 1) { drush_set_error('MAKE_MULTIPLE_CORES', dt('More than one core project specified.')); return FALSE; } foreach ($projects as $type => $type_projects) { foreach ($type_projects as $project) { if (make_project_needs_release_info($project)) { // For convenience: define $request to be compatible with release_info // engine. // TODO: refactor to enforce 'make' to internally work with release_info // keys. $request = make_prepare_request($project, $type); // Set the cache option based on our '--no-cache' option. $cache_before = drush_get_option('cache'); if (!drush_get_option('no-cache', FALSE)) { drush_set_option('cache', TRUE); } $release = release_info_fetch($request); // Restore the previous '--cache' option value. drush_set_option('cache', $cache_before); if (!isset($project['type'])) { // Translate release_info key for project_type to drush make. $project['type'] = $request['project_type']; } if (!isset($project['download'])) { $project['download'] = array( 'type' => 'pm', 'full_version' => $release['version'], 'download_link' => $release['download_link'], 'status url' => $request['status url'], ); } } if (!empty($md5)) { $project['download']['md5'] = $md5; } if ($ignore_checksums) { unset($project['download']['md5']); } $projects[($project['type'] == 'core' ? 'core' : 'contrib')][$project['name']] = $project; } } // Core is built in place, rather than using make-process. if (isset($projects['core'])) { foreach ($projects['core'] as $project) { if ($instance = DrushMakeProject::getInstance($project['type'], $project)) { $project = $instance; } else { make_error('PROJECT-TYPE', dt('Non-existent project type %type on project %project', array('%type' => $project['type'], '%project' => $project['name']))); } $project->make(); } } // Process all projects concurrently using make-process. if (isset($projects['contrib'])) { $invocations = array(); foreach ($projects['contrib'] as $project) { $invocations[] = array( 'args' => array( make_tmp(), ), 'options' => array( 'project' => $project, ), 'site' => array(), ); } if (!empty($invocations)) { // Backend options. $backend_options = array( 'concurrency' => drush_get_option('concurrency', 1), 'method' => 'POST', ); $common_options = drush_redispatch_get_options(); // Merge in stdin options since we process makefiles recursively. See http://drupal.org/node/1510180. $common_options = array_merge($common_options, drush_get_context('stdin')); // Package handler should use 'wget'. $common_options['package-handler'] = 'wget'; // Avoid any prompts from CLI. $common_options['yes'] = TRUE; // Use cache unless explicitly turned off. if (!drush_get_option('no-cache', FALSE)) { $common_options['cache'] = TRUE; } // Unless --verbose or --debug are passed, quiter backend output. if (empty($common_options['verbose']) && empty($common_options['debug'])) { $backend_options['#output-label'] = FALSE; $backend_options['integrate'] = TRUE; } drush_backend_invoke_concurrent($invocations, $common_options, $backend_options, 'make-process', '@none'); } } return TRUE; } /** * Process all libraries specified in the make file. */ function make_libraries($contrib_destination, $info, $build_path) { if (empty($info['libraries'])) { return; } $ignore_checksums = drush_get_option('ignore-checksums'); foreach ($info['libraries'] as $key => $library) { if (!is_string($key) || !is_array($library)) { // TODO Print a prettier message. continue; } // Merge the known data onto the library info. $library += array( 'name' => $key, 'core' => $info['core'], 'build_path' => $build_path, 'contrib_destination' => $contrib_destination, 'subdir' => '', 'directory_name' => $key, ); if ($ignore_checksums) { unset($library['download']['md5']); } $class = DrushMakeProject::getInstance('library', $library); $class->make(); } } /** * The path where the final build will be placed. */ function make_build_path($build_path) { static $saved_path; if (isset($saved_path)) { return $saved_path; } // Determine the base of the build. if (drush_get_option('tar')) { $build_path = dirname($build_path) . '/' . basename($build_path, '.tar.gz') . '.tar.gz'; } elseif (isset($build_path) && (!empty($build_path) || $build_path == '.')) { $build_path = rtrim($build_path, '/'); } // Allow tests to run without a specified base path. elseif (drush_get_option('test') || drush_confirm(dt("Make new site in the current directory?"))) { $build_path = '.'; } else { return drush_user_abort(dt('Build aborted.')); } if ($build_path != '.' && file_exists($build_path)) { return drush_set_error('MAKE_PATH_EXISTS', dt('Base path %path already exists', array('%path' => $build_path))); } $saved_path = $build_path; return $build_path; } /** * Move the completed build into place. */ function make_move_build($build_path) { $tmp_path = make_tmp(); $ret = TRUE; if ($build_path == '.') { $info = drush_scan_directory($tmp_path . DIRECTORY_SEPARATOR . '__build__', '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE); foreach ($info as $file) { $destination = $build_path . DIRECTORY_SEPARATOR . $file->basename; if (file_exists($destination)) { // To prevent the removal of top-level directories such as 'modules' or // 'themes', descend in a level if the file exists. // TODO: This only protects one level of directories from being removed. $files = drush_scan_directory($file->filename, '/./', array('.', '..'), 0, FALSE); foreach ($files as $file) { $ret = $ret && drush_copy_dir($file->filename, $destination . DIRECTORY_SEPARATOR . $file->basename, FILE_EXISTS_MERGE); } } else { $ret = $ret && drush_copy_dir($file->filename, $destination); } } } else { drush_mkdir(dirname($build_path)); $ret = drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . basename($build_path), TRUE); $ret = $ret && drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . basename($build_path), $build_path); } // Copying to final destination resets write permissions. Re-apply. if (drush_get_option('prepare-install')) { $default = $build_path . '/sites/default'; chmod($default . '/settings.php', 0666); chmod($default . '/files', 0777); } if (!$ret) { drush_set_error('MAKE_CANNOT_MOVE_BUILD', dt("Cannot move build into place")); } return $ret; } /** * Create a request array for use with release_info_fetch(). * * @param array $project * Project array. * @param string $type * 'contrib' or 'core'. */ function make_prepare_request($project, $type = 'contrib') { $request = array( 'name' => $project['name'], 'drupal_version' => $project['core'], 'status url' => $project['location'], ); if ($project['version'] != '') { $request['project_version'] = $project['version']; $request['version'] = $type == 'core' ? $project['version'] : $project['core'] . '-' . $project['version']; } return $request; } /** * Determine if the release information is required for this * project. When it is determined that it is, this potentially results * in the use of pm-download to process the project. * * If the location of the project is not customized (uses d.o), and * one of the following is true, then release information is required: * * - $project['type'] has not been specified * - $project['download'] has not been specified * * @see make_projects() */ function make_project_needs_release_info($project) { return isset($project['location']) // Only fetch release info if the project type is unknown OR if // download attributes are unspecified. && (!isset($project['type']) || !isset($project['download'])); } drush-5.10.0/commands/make/make.project.inc000066400000000000000000000505201222105546100205150ustar00rootroot00000000000000 $value) { $this->{$key} = $value; } } /** * Get an instance for the type and project. * * @param string $type * Type of project: core, library, module, profile, or translation. * @param array $project * Project information. * * @return mixed * An instance for the project or FALSE if invalid type. */ public static function getInstance($type, $project) { if (!isset(self::$self[$type][$project['name']])) { $class = 'DrushMakeProject_' . $type; self::$self[$type][$project['name']] = class_exists($class) ? new $class($project) : FALSE; } return self::$self[$type][$project['name']]; } /** * Download a project. */ function download() { $this->downloaded = TRUE; // In some cases, make_download_factory() is going to need to know the // full version string of the project we're trying to download. However, // the version is a project-level attribute, not a download-level // attribute. So, if we don't already have a full version string in the // download array (e.g. if it was initialized via the release history XML // for the PM case), we take the version info from the project-level // attribute, convert it into a full version string, and stuff it into // $this->download so that the download backend has access to it, too. if (!empty($this->version) && empty($this->download['full_version'])) { $full_version = ''; $matches = array(); // Core needs different conversion rules than contrib. if (!empty($this->type) && $this->type == 'core') { // Generally, the version for core is already set properly. $full_version = $this->version; // However, it might just be something like '7' or '7.x', in which // case we need to turn that into '7.x-dev'; if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) { // If there's no '.x' already, append it. if (empty($matches[1])) { $full_version .= '.x'; } $full_version .= '-dev'; } } // Contrib. else { // If the version doesn't already define a core version, prepend it. if (!preg_match('/^\d+\.x-\d+.*$/', $this->version)) { // Just find the major version from $this->core so we don't end up // with version strings like '7.12-2.0'. $core_parts = explode('.', $this->core); $full_version = $core_parts[0] . '.x-'; } $full_version .= $this->version; // If the project-level version attribute is just a number it's a major // version. if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) { // If there's no '.x' already, append it. if (empty($matches[1])) { $full_version .= '.x'; } $full_version .= '-dev'; } } $this->download['full_version'] = $full_version; } if (make_download_factory($this->name, $this->type, $this->download, $this->download_location) === FALSE) { $this->downloaded = FALSE; } return $this->downloaded; } /** * Build a project. */ function make() { if ($this->made) { drush_log(dt('Attempt to build project @project more then once prevented.', array('@project' => $this->name))); return TRUE; } $this->made = TRUE; if (is_null($this->download_location)) { $this->download_location = $this->findDownloadLocation(); } if ($this->download() === FALSE) { return FALSE; } if (!$this->addLockfile($this->download_location)) { return FALSE; } if (!$this->applyPatches($this->download_location)) { return FALSE; } if (!$this->getTranslations($this->download_location)) { return FALSE; } // Handle .info file re-writing (if so desired). if (!drush_get_option('no-gitinfofile', FALSE) && isset($this->download['type']) && $this->download['type'] == 'git') { $this->processGitInfoFiles(); } // Clean-up .git directories. if (!drush_get_option('working-copy')) { $this->removeGitDirectory(); } if (!$this->recurse($this->download_location)) { return FALSE; } return TRUE; } /** * Determine the location to download project to. */ function findDownloadLocation() { $this->path = $this->generatePath(); $this->project_directory = !empty($this->directory_name) ? $this->directory_name : $this->name; $this->download_location = $this->path . '/' . $this->project_directory; // This directory shouldn't exist yet -- if it does, stop, // unless overwrite has been set to TRUE. if (is_dir($this->download_location) && !$this->overwrite) { drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location))); return FALSE; } elseif ($this->download['type'] === 'pm') { // pm-download will create the final contrib directory. drush_mkdir(dirname($this->download_location)); } else { drush_mkdir($this->download_location); } return $this->download_location; } /** * Retrieve and apply any patches specified by the makefile to this project. */ function applyPatches($project_directory) { if (empty($this->patch)) { return TRUE; } $patches_txt = ''; $ignore_checksums = drush_get_option('ignore-checksums'); foreach ($this->patch as $info) { if (!is_array($info)) { $info = array('url' => $info); } // Download the patch. if ($filename = _make_download_file($info['url'])) { $patched = FALSE; $output = ''; // Test each patch style; -p1 is the default with git. See // http://drupal.org/node/1054616 $patch_levels = array('-p1', '-p0'); foreach ($patch_levels as $patch_level) { $checked = drush_shell_exec('cd %s && GIT_DIR=. git apply --check %s %s --verbose', $project_directory, $patch_level, $filename); if ($checked) { // Apply the first successful style. $patched = drush_shell_exec('cd %s && GIT_DIR=. git apply %s %s --verbose', $project_directory, $patch_level, $filename); break; } } // In some rare cases, git will fail to apply a patch, fallback to using // the 'patch' command. if (!$patched) { foreach ($patch_levels as $patch_level) { // --no-backup-if-mismatch here is a hack that fixes some // differences between how patch works on windows and unix. if ($patched = drush_shell_exec("patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $project_directory, $filename)) { break; } } } if ($output = drush_shell_exec_output()) { // Log any command output, visible only in --verbose or --debug mode. drush_log(implode("\n", $output)); } // Set up string placeholders to pass to dt(). $dt_args = array( '@name' => $this->name, '@filename' => basename($filename), ); if ($patched) { if (!$ignore_checksums && !_make_verify_checksums($info, $filename)) { return FALSE; } $patches_txt .= '- ' . $info['url'] . "\n"; drush_log(dt('@name patched with @filename.', $dt_args), 'ok'); } else { make_error('PATCH_ERROR', dt("Unable to patch @name with @filename.", $dt_args)); } drush_op('unlink', $filename); } else { make_error('DOWNLOAD_ERROR', 'Unable to download ' . $info['url'] . '.'); return FALSE; } } if (!empty($patches_txt) && !drush_get_option('no-patch-txt') && !file_exists($project_directory . '/PATCHES.txt')) { $patches_txt = "The following patches have been applied to this project:\n" . $patches_txt . "\nThis file was automatically generated by Drush Make (http://drupal.org/project/drush)."; file_put_contents($project_directory . '/PATCHES.txt', $patches_txt); drush_log('Generated PATCHES.txt file for ' . $this->name, 'ok'); } return TRUE; } /** * Process info files when downloading things from git. */ function processGitInfoFiles() { // Bail out if this isn't hosted on Drupal.org. if (isset($this->download['url']) && strpos($this->download['url'], 'drupal.org') === FALSE) { return; } // Figure out the proper version string to use based on the .make file. // Best case is the .make file author told us directly. if (!empty($this->download['full_version'])) { $full_version = $this->download['full_version']; } // Next best is if we have a tag, since those are identical to versions. elseif (!empty($this->download['tag'])) { $full_version = $this->download['tag']; } // If we have a branch, append '-dev'. elseif (!empty($this->download['branch'])) { $full_version = $this->download['branch'] . '-dev'; } // Ugh. Not sure what else we can do in this case. elseif (!empty($this->download['revision'])) { $full_version = $this->download['revision']; } // Probably can never reach this case. else { $full_version = 'unknown'; } // If the version string ends in '.x-dev' do the Git magic to figure out // the appropriate 'rebuild version' string, e.g. '7.x-1.2+7-dev'. $matches = array(); if (preg_match('/^(.+).x-dev$/', $full_version, $matches)) { require_once dirname(__FILE__) . '/../pm/package_handler/git_drupalorg.inc'; $rebuild_version = drush_pm_git_drupalorg_compute_rebuild_version($this->download_location, $matches[1]); if ($rebuild_version) { $full_version = $rebuild_version; } } require_once dirname(__FILE__) . '/../pm/pm.drush.inc'; drush_pm_inject_info_file_metadata($this->download_location, $this->name, $full_version); } /** * Remove the .git directory from a project. */ function removeGitDirectory() { if (isset($this->download['type']) && $this->download['type'] == 'git' && file_exists($this->download_location . '/.git')) { drush_delete_dir($this->download_location . '/.git', TRUE); } } /** * Add a lock file. */ function addLockfile($project_directory) { if (!empty($this->lock)) { file_put_contents($project_directory . '/.drush-lock-update', $this->lock); } return TRUE; } /** * Retrieve translations for this project. */ function getTranslations($project_directory) { static $cache = array(); $langcodes = $this->translations; if ($langcodes && in_array($this->type, array('core', 'module', 'profile', 'theme'), TRUE)) { // Support the l10n_path, l10n_url keys from l10n_update. Note that the // l10n_server key is not supported. if (isset($this->l10n_path)) { $update_url = $this->l10n_path; } else { if (isset($this->l10n_url)) { $l10n_server = $this->l10n_url; } else { $l10n_server = FALSE; } if ($l10n_server) { if (!isset($cache[$l10n_server])) { if ($filename = _make_download_file($l10n_server)) { $server_info = simplexml_load_string(file_get_contents($filename)); $cache[$l10n_server] = !empty($server_info->update_url) ? $server_info->update_url : FALSE; } } if ($cache[$l10n_server]) { $update_url = $cache[$l10n_server]; } else { make_error('XML_ERROR', dt("Could not retrieve l10n update url for !project.", array('!project' => $project['name']))); return FALSE; } } } if ($update_url) { $failed = array(); foreach ($langcodes as $langcode) { $variables = array( '%project' => $this->name, '%release' => $this->download['full_version'], '%core' => $this->core, '%language' => $langcode, '%filename' => '%filename', ); $url = strtr($update_url, $variables); // Download the translation file. Since its contents are volatile, // cache for only 4 hours. if ($filename = _make_download_file($url, 3600 * 4)) { // If this is the core project type, download the translation file // and place it in every profile and an additional copy in // modules/system/translations where it can be detected for import // by other non-default install profiles. if ($this->type === 'core') { $profiles = drush_scan_directory($project_directory . '/profiles', '/.*/', array(), 0, FALSE, 'filename', 0, TRUE); foreach ($profiles as $profile) { if (is_dir($project_directory . '/profiles/' . $profile->basename)) { drush_mkdir($project_directory . '/profiles/' . $profile->basename . '/translations'); drush_copy_dir($filename, $project_directory . '/profiles/' . $profile->basename . '/translations/' . $langcode . '.po'); } } drush_mkdir($project_directory . '/modules/system/translations'); drush_copy_dir($filename, $project_directory . '/modules/system/translations/' . $langcode . '.po'); } else { drush_mkdir($project_directory . '/translations'); drush_copy_dir($filename, $project_directory . '/translations/' . $langcode . '.po', TRUE); } } else { $failed[] = $langcode; } } if (empty($failed)) { drush_log('All translations downloaded for ' . $this->name, 'ok'); } else { drush_log('Unable to download translations for ' . $this->name . ': ' . implode(', ', $failed), 'warning'); } } } return TRUE; } /** * Generate the proper path for this project type. * * @param boolean $base * Whether include the base part (tmp dir). Defaults to TRUE. */ protected function generatePath($base = TRUE) { $path = array(); if ($base) { $path[] = make_tmp(); $path[] = '__build__'; } if (!empty($this->contrib_destination)) { $path[] = $this->contrib_destination; } if (!empty($this->subdir)) { $path[] = $this->subdir; } return implode('/', $path); } /** * Return the proper path for dependencies to be placed in. * * @return string * The path that dependencies will be placed in. */ protected function buildPath($directory) { return $this->base_contrib_destination; } /** * Recurse to process additional makefiles that may be found during * processing. */ function recurse($path) { $makefile = $this->download_location . '/' . $this->name . '.make'; if (!file_exists($makefile)) { $makefile = $this->download_location . '/drupal-org.make'; if (!file_exists($makefile)) { return TRUE; } } drush_log(dt("Found makefile: !makefile", array("!makefile" => basename($makefile))), 'ok'); $info = make_parse_info_file($makefile); if (!($info = make_validate_info_file($info))) { return FALSE; } $build_path = $this->buildPath($this->name); make_projects(TRUE, trim($build_path, '/'), $info, $this->build_path); make_libraries(trim($build_path, '/'), $info, $this->build_path); return TRUE; } } /** * For processing Drupal core projects. */ class DrushMakeProject_Core extends DrushMakeProject { /** * Override constructor for core to adjust project info. */ protected function __construct(&$project) { parent::__construct($project); // subdir and contrib_destination are not allowed for core. $this->subdir = ''; $this->contrib_destination = ''; } /** * Determine the location to download project to. */ function findDownloadLocation() { $this->path = $this->download_location = $this->generatePath(); $this->project_directory = ''; if (is_dir($this->download_location)) { drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location))); return FALSE; } elseif ($this->download['type'] === 'pm') { // pm-download will create the final __build__ directory, so nothing to do // here. } else { drush_mkdir($this->download_location); } return $this->download_location; } } /** * For processing libraries. */ class DrushMakeProject_Library extends DrushMakeProject { /** * Override constructor for libraries to properly set contrib destination. */ protected function __construct(&$project) { parent::__construct($project); // Allow libraries to specify where they should live in the build path. if (isset($project['destination'])) { $project_path = $project['destination']; } else { $project_path = 'libraries'; } $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . $project_path; } /** * No recursion for libraries, sorry :-( */ function recurse($path) { // Return TRUE so that processing continues in the make() method. return TRUE; } /** * No translations for libraries. */ function getTranslations($download_location) { // Return TRUE so that processing continues in the make() method. return TRUE; } } /** * For processing modules. */ class DrushMakeProject_Module extends DrushMakeProject { /** * Override constructor for modules to properly set contrib destination. */ protected function __construct(&$project) { parent::__construct($project); $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'modules'; } } /** * For processing installation profiles. */ class DrushMakeProject_Profile extends DrushMakeProject { /** * Override contructor for installation profiles to properly set contrib * destination. */ protected function __construct(&$project) { parent::__construct($project); $this->contrib_destination = (!empty($this->destination) ? $this->destination : 'profiles'); } /** * Find the build path. */ protected function buildPath($directory) { return $this->generatePath(FALSE) . '/' . $directory; } } /** * For processing themes. */ class DrushMakeProject_Theme extends DrushMakeProject { /** * Override contructor for themes to properly set contrib destination. */ protected function __construct(&$project) { parent::__construct($project); $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'themes'; } } /** * For processing translations. */ class DrushMakeProject_Translation extends DrushMakeProject { /** * Override constructor for translations to properly set contrib destination. */ protected function __construct(&$project) { parent::__construct($project); switch ($project['core']) { case '5.x': // Don't think there's an automatic place we can put 5.x translations, // so we'll toss them in a translations directory in the Drupal root. $this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'translations'; break; default: $this->contrib_destination = ''; break; } } } drush-5.10.0/commands/make/make.utilities.inc000066400000000000000000000407551222105546100210730ustar00rootroot00000000000000 $makefile))); } if (!($info = _drush_drupal_parse_info_file($data))) { return FALSE; } if (!empty($info['includes'])) { $include_path = dirname($makefile); $includes = array(); if (!empty($info['includes']) && is_array($info['includes'])) { foreach ($info['includes'] as $include) { if (is_string($include)) { if (make_valid_url($include, TRUE) && ($file = make_parse_info_file($include, FALSE))) { $includes[] = $file; } elseif (file_exists($include_path . '/' . $include) && ($file = make_parse_info_file($include_path . '/' . $include, FALSE))) { $includes[] = $file; } elseif (file_exists($include) && ($file = make_parse_info_file($include, FALSE))) { $includes[] = $file; } else { make_error('BUILD_ERROR', dt("Include file missing: !include", array('!include' => $include))); } } } } $includes[] = $data; $data = implode("\n", $includes); $info = _drush_drupal_parse_info_file($data); } if ($parsed) { return $info; } else { return $data; } } /** * Remove entries in the info file in accordance with the options passed in. * Entries are either explicitly 'allowed' (with the $include_only parameter) in * which case all *other* entries will be excluded. * * @param array $info * A parsed info file. * * @param array $include_only * (Optional) Array keyed by entry type (e.g. 'libraries') against an array of * allowed keys for that type. The special value '*' means 'all entries of * this type'. If this parameter is omitted, no entries will be excluded. * * @return array * The $info array, pruned if necessary. */ function make_prune_info_file($info, $include_only = array()) { // We may get passed FALSE in some cases. // Also we cannot prune an empty array, so no point in this code running! if (empty($info)) { return $info; } // We will accrue an explanation of our activities here. $msg = array(); $msg['scope'] = dt("Drush make restricted to the following entries:"); $pruned = FALSE; if (count(array_filter($include_only))) { $pruned = TRUE; foreach ($include_only as $type => $keys) { if (!isset($info[$type])) { continue; } // For translating // dt("Projects"); // dt("Libraries"); $type_title = dt(ucfirst($type)); // Handle the special '*' value. if (in_array('*', $keys)) { $msg[$type] = dt("!entry_type: ", array('!entry_type' => $type_title)); } // Handle a (possibly empty) array of keys to include/exclude. else { $info[$type] = array_intersect_key($info[$type], array_fill_keys($keys, 1)); unset($msg[$type]); if (!empty($info[$type])) { $msg[$type] = dt("!entry_type: !make_entries", array('!entry_type' => $type_title, '!make_entries' => implode(', ', array_keys($info[$type])))); } } } } if ($pruned) { // Make it clear to the user what's going on. drush_log(implode("\n", $msg), 'ok'); // Throw an error if these restrictions reduced the make to nothing. if (empty($info['projects']) && empty($info['libraries'])) { // This error mentions the options explicitly to make it as clear as // possible to the user why this error has occurred. make_error('BUILD_ERROR', dt("All projects and libraries have been excluded. Review the 'projects' and 'libraries' options.")); } } return $info; } /** * Validate the make file. */ function make_validate_info_file($info) { // Assume no errors to start. $errors = FALSE; if (empty($info['core'])) { make_error('BUILD_ERROR', dt("The 'core' attribute is required")); $errors = TRUE; } // Standardize on core. elseif (preg_match('/^(\d+)(\.(x|(\d+)(-[a-z0-9]+)?))?$/', $info['core'], $matches)) { // An exact version of core has been specified, so pass that to an // internal variable for storage. if (isset($matches[4])) { $info['core_release'] = $info['core']; } // Format the core attribute consistently. $info['core'] = $matches[1] . '.x'; } else { make_error('BUILD_ERROR', dt("The 'core' attribute !core has an incorrect format.", array('!core' => $info['core']))); $errors = TRUE; } if (!isset($info['api'])) { $info['api'] = MAKE_API; drush_log(dt("You need to specify an API version of two in your makefile:\napi = !api", array("!api" => MAKE_API)), 'warning'); } elseif ($info['api'] != MAKE_API) { make_error('BUILD_ERROR', dt("The specified API attribute is incompatible with this version of Drush Make.")); $errors = TRUE; } $names = array(); // Process projects. if (isset($info['projects'])) { if (!is_array($info['projects'])) { make_error('BUILD_ERROR', dt("'projects' attribute must be an array.")); $errors = TRUE; } else { // Filter out entries that have been forcibly removed via [foo] = FALSE. $info['projects'] = array_filter($info['projects']); foreach ($info['projects'] as $project => $project_data) { // Project has an attributes array. if (is_string($project) && is_array($project_data)) { if (in_array($project, $names)) { make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project))); $errors = TRUE; } $names[] = $project; foreach ($project_data as $attribute => $value) { // Unset disallowed attributes. if (in_array($attribute, array('contrib_destination'))) { unset($info['projects'][$project][$attribute]); } // Prevent malicious attempts to access other areas of the // filesystem. elseif (in_array($attribute, array('subdir', 'directory_name')) && !make_safe_path($value)) { $args = array( '!path' => $value, '!attribute' => $attribute, '!project' => $project, ); make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in project !project.", $args)); $errors = TRUE; } } } // Cover if there is no project info, it's just a project name. elseif (is_numeric($project) && is_string($project_data)) { if (in_array($project_data, $names)) { make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project_data))); $errors = TRUE; } $names[] = $project_data; unset($info['projects'][$project]); $info['projects'][$project_data] = array(); } // Convert shorthand project version style to array format. elseif (is_string($project_data)) { if (in_array($project, $names)) { make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project))); $errors = TRUE; } $names[] = $project; $info['projects'][$project] = array('version' => $project_data); } else { make_error('BUILD_ERROR', dt('Project !project incorrectly specified.', array('!project' => $project))); $errors = TRUE; } } } } if (isset($info['libraries'])) { if (!is_array($info['libraries'])) { make_error('BUILD_ERROR', dt("'libraries' attribute must be an array.")); $errors = TRUE; } else { // Filter out entries that have been forcibly removed via [foo] = FALSE. $info['libraries'] = array_filter($info['libraries']); foreach ($info['libraries'] as $library => $library_data) { if (is_array($library_data)) { foreach ($library_data as $attribute => $value) { // Unset disallowed attributes. if (in_array($attribute, array('contrib_destination'))) { unset($info['libraries'][$library][$attribute]); } // Prevent malicious attempts to access other areas of the // filesystem. elseif (in_array($attribute, array('contrib-destination', 'directory_name')) && !make_safe_path($value)) { $args = array( '!path' => $value, '!attribute' => $attribute, '!library' => $library, ); make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in library !library.", $args)); $errors = TRUE; } } } } } } // Apply defaults after projects[] array has been expanded, but prior to // external validation. make_apply_defaults($info); foreach (drush_command_implements('make_validate_info') as $module) { $function = $module . '_make_validate_info'; $return = $function($info); if ($return) { $info = $return; } else { $errors = TRUE; } } if ($errors) { return FALSE; } return $info; } /** * Verify the syntax of the given URL. * * Copied verbatim from includes/common.inc * * @see valid_url */ function make_valid_url($url, $absolute = FALSE) { if ($absolute) { return (bool) preg_match(" /^ # Start at the beginning of the text (?:ftp|https?):\/\/ # Look for ftp, http, or https schemes (?: # Userinfo (optional) which is typically (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination )? (?: (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address ) (?::[0-9]+)? # Server port number (optional) (?:[\/|\?] (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional) *)? $/xi", $url); } else { return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url); } } /** * Find, and possibly create, a temporary directory. * * @param boolean $set * Must be TRUE to create a directory. * @param string $directory * Pass in a directory to use. This is required if using any * concurrent operations. * * @todo Merge with drush_tempdir(). */ function make_tmp($set = TRUE, $directory = NULL) { static $tmp_dir; if (isset($directory) && !isset($tmp_dir)) { $tmp_dir = $directory; } if (!isset($tmp_dir) && $set) { $tmp_dir = drush_find_tmp(); if (strrpos($tmp_dir, '/') == strlen($tmp_dir) - 1) { $tmp_dir .= 'make_tmp_' . time() . '_' . uniqid(); } else { $tmp_dir .= '/make_tmp_' . time() . '_' . uniqid(); } if (!drush_get_option('no-clean', FALSE)) { drush_register_file_for_deletion($tmp_dir); } if (file_exists($tmp_dir)) { return make_tmp(TRUE); } // Create the directory. drush_mkdir($tmp_dir); } return $tmp_dir; } /** * Removes the temporary build directory. On failed builds, this is handled by * drush_register_file_for_deletion(). */ function make_clean_tmp() { if (!($tmp_dir = make_tmp(FALSE))) { return; } if (!drush_get_option('no-clean', FALSE)) { drush_delete_dir($tmp_dir); } else { drush_log(dt('Temporary directory: !dir', array('!dir' => $tmp_dir)), 'ok'); } } /** * Prepare a Drupal installation, copying default.settings.php to settings.php. */ function make_prepare_install($build_path) { $default = make_tmp() . '/__build__/sites/default'; drush_copy_dir($default . DIRECTORY_SEPARATOR . 'default.settings.php', $default . DIRECTORY_SEPARATOR . 'settings.php', TRUE); drush_mkdir($default . '/files'); chmod($default . DIRECTORY_SEPARATOR . 'settings.php', 0666); chmod($default . DIRECTORY_SEPARATOR . 'files', 0777); } /** * Calculate a cksum on each file in the build, and md5 the resulting hashes. */ function make_md5() { return drush_dir_md5(make_tmp()); } /** * @todo drush_archive_dump() also makes a tar. Consolidate? */ function make_tar($build_path) { $tmp_path = make_tmp(); drush_mkdir(dirname($build_path)); $filename = basename($build_path); $dirname = basename($build_path, '.tar.gz'); // Move the build directory to a more human-friendly name, so that tar will // use it instead. drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . $dirname, TRUE); // Only move the tar file to it's final location if it's been built // successfully. if (drush_shell_exec("%s -C %s -Pczf %s %s", drush_get_tar_executable(), $tmp_path, $tmp_path . '/' . $filename, $dirname)) { drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $filename, $build_path, TRUE); }; // Move the build directory back to it's original location for consistency. drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $dirname, $tmp_path . DIRECTORY_SEPARATOR . '__build__'); } /** * Logs an error unless the --force-complete command line option is specified. */ function make_error($error_code, $message) { if (drush_get_option('force-complete')) { drush_log("$error_code: $message -- build forced", 'warning'); } else { drush_set_error($error_code, $message); } } /** * Checks an attribute's path to ensure it's not maliciously crafted. * * @param string $path * The path to check. */ function make_safe_path($path) { return !preg_match("+^/|^\.\.|/\.\./+", $path); } /** * Get data based on the source. * * This is a helper function to abstract the retrieval of data, so that it can * come from files, STDIN, etc. Currently supports filepath and STDIN. * * @param string $data_source * The path to a file, or '-' for STDIN. * * @return string * The raw data as a string. */ function make_get_data($data_source) { if ($data_source == '-') { // See http://drupal.org/node/499758 before changing this. $stdin = fopen('php://stdin', 'r'); $data = ''; $has_input = FALSE; while ($line = fgets($stdin)) { $has_input = TRUE; $data .= $line; } if ($has_input) { return $data; } return FALSE; } // Local file. elseif (!strpos($data_source, '://')) { $data = file_get_contents($data_source); } // Remote file. else { $file = _make_download_file($data_source); $data = file_get_contents($file); drush_op('unlink', $file); } return $data; } /** * Helper to provide sys_get_temp_dir if on php < 5.2.1. */ if (!function_exists('sys_get_temp_dir')) { /** * Based on * http://www.phpit.net/article/creating-zip-tar-archives-dynamically-php/2/ */ function sys_get_temp_dir() { // Try to get from environment variable. if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); } elseif (!empty($_ENV['TMPDIR'])) { return realpath($_ENV['TMPDIR']); } elseif (!empty($_ENV['TEMP'])) { return realpath($_ENV['TEMP']); } else { // Detect by creating a temporary file. // Try to use system's temporary directory as random name // shouldn't exist. $temp_file = tempnam(md5(uniqid(rand(), TRUE)), ''); if ($temp_file) { $temp_dir = realpath(dirname($temp_file)); unlink($temp_file); return $temp_dir; } else { return FALSE; } } } } /** * Apply any defaults. * * @param array &$info * A parsed make array. */ function make_apply_defaults(&$info) { if (isset($info['defaults'])) { $defaults = $info['defaults']; unset($info['defaults']); foreach ($defaults as $type => $default_data) { if (isset($info[$type])) { foreach ($info[$type] as $project => $data) { $info[$type][$project] += $default_data; } } else { drush_log(dt("Unknown attribute '@type' in defaults array", array('@type' => $type)), 'warning'); } } } } drush-5.10.0/commands/pm/000077500000000000000000000000001222105546100151355ustar00rootroot00000000000000drush-5.10.0/commands/pm/download.pm.inc000066400000000000000000000361221222105546100200560ustar00rootroot00000000000000 $destination))); if (!drush_get_context('DRUSH_SIMULATE')) { if (drush_confirm(dt('Would you like to create it?'))) { drush_mkdir($destination, TRUE); } if (!is_dir($destination)) { return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination))); } } } if (!is_writable($destination)) { return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination))); } // Ignore --use-site-dir, if given. if (drush_get_option('use-site-dir', FALSE)) { drush_set_option('use-site-dir', FALSE); } } // Validate --variant or enforce a sane default. $variant = drush_get_option('variant', FALSE); if ($variant) { $variants = array('full', 'projects', 'profile-only'); if (!in_array($variant, $variants)) { return drush_set_error('DRUSH_PM_PROFILE_INVALID_VARIANT', dt('Invalid variant !variant. Valid values: !variants.', array('!variant' => $variant, '!variants' => implode(', ', $variants)))); } } // 'full' and 'projects' variants are only valid for wget package handler. $package_handler = drush_get_option('package-handler', 'wget'); if (($package_handler != 'wget') && ($variant != 'profile-only')) { $new_variant = 'profile-only'; if ($variant) { drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), 'warning'); } } // If we are working on a drupal root, full variant is not an option. else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { if ((!$variant) || (($variant == 'full') && (!isset($new_variant)))) { $new_variant = 'projects'; } if ($variant == 'full') { drush_log(dt('Variant full is not a valid option within a Drupal root.'), 'warning'); } } if (isset($new_variant)) { drush_set_option('variant', $new_variant); if ($variant) { drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), 'ok'); } } } /** * Command callback. Download Drupal core or any project. */ function drush_pm_download() { if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) { $requests = array('drupal'); } // Parse out project name and version. $requests = pm_parse_project_version($requests); // Get release history for each request and download the project. $source = drush_get_option('source', RELEASE_INFO_DEFAULT_URL); $restrict_to = drush_get_option('dev', FALSE) ? 'dev' : ''; $select = drush_get_option('select', 'auto'); $all = drush_get_option('all', FALSE); foreach ($requests as $name => $request) { $request['status url'] = $source; $release = release_info_fetch($request, $restrict_to, $select, $all); if ($release == FALSE) { continue; } // Determine the name of the directory that will contain the project. // We face here all the asymetries to make it smooth for package handlers. // For Drupal core: --drupal-project-rename or drupal-x.y if (($request['project_type'] == 'core') || (($request['project_type'] == 'profile') && (drush_get_option('variant', 'full') == 'full'))) { // Avoid downloading core into existing core. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) { if (strpos(realpath(drush_get_option('destination')), DRUPAL_ROOT) !== FALSE) { return drush_set_error('DRUSH_PM_DOWNLOAD_TRANSLATIONS_FORBIDDEN', dt('It\'s forbidden to download !project core into an existing core.', array('!project' => $request['name']))); } } if ($rename = drush_get_option('drupal-project-rename', FALSE)) { if ($rename === TRUE) { $request['project_dir'] = $request['name']; } else { $request['project_dir'] = $rename; } } else { // Set to drupal-x.y, the expected name for .tar.gz contents. // Explicitly needed for cvs package handler. $request['project_dir'] = strtolower(strtr($release['name'], ' ', '-')); } } // For the other project types we want the project name. Including core // variant for profiles. Note those come with drupal-x.y in the .tar.gz. else { $request['project_dir'] = $request['name']; } // Download the project to a temporary location. $request['base_project_path'] = drush_tempdir(); $request['full_project_path'] = $request['base_project_path'] .'/'. $request['project_dir']; drush_log(dt('Downloading project !name to !dir ...', array('!name' => $request['name'], '!dir' => $request['base_project_path']))); if (!package_handler_download_project($request, $release)) { drush_log(dt('Error downloading !name', array('!name' => $request['name']), 'error')); continue; } // Determine the install location for the project. User provided // --destination has preference. $destination = drush_get_option('destination'); if (!empty($destination)) { if (!file_exists($destination)) { drush_mkdir($destination); } $request['project_install_location'] = realpath($destination); } else { $request['project_install_location'] = _pm_download_destination($request['project_type']); } // If user did not provide --destination, then call the // download-destination-alter hook to give the chance to any commandfiles // to adjust the install location or abort it. if (empty($destination)) { $result = drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release); if (array_search(FALSE, $result, TRUE) !== FALSE) { return FALSE; } } // Load version control engine and detect if (the parent directory of) the // project install location is under a vcs. if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) { continue; } $request['project_install_location'] .= '/' . $request['project_dir']; if ($version_control->engine == 'backup') { // Check if install location already exists. if (is_dir($request['project_install_location'])) { if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) { drush_delete_dir($request['project_install_location'], TRUE); } else { drush_log(dt("Skip installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), 'warning'); continue; } } } else { // Find and unlink all files but the ones in the vcs control directories. $skip_list = array('.', '..'); $skip_list = array_merge($skip_list, drush_version_control_reserved_files()); drush_scan_directory($request['project_install_location'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE); } // Copy the project to the install location. if (drush_op('_drush_recursive_copy', $request['full_project_path'], $request['project_install_location'])) { drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), 'success'); $request['base_project_path'] = basename($request['project_install_location']); $request['full_project_path'] = $request['project_install_location']; if ($request['project_install_location'] == DRUSH_BASE_PATH) { drush_log(dt("Drush successfully updated to version !version.", array('!version' => $release['version'])), 'success'); } // If the version control engine is a proper vcs we also need to remove // orphan directories. if ($version_control->engine != 'backup') { $empty_dirs = drush_find_empty_directories($request['full_project_path'], $version_control->reserved_files()); foreach ($empty_dirs as $empty_dir) { // Some VCS files are read-only on Windows (e.g., .svn/entries). drush_delete_dir($empty_dir, TRUE); } } // Post download actions. package_handler_post_download($request, $release); drush_command_invoke_all('drush_pm_post_download', $request, $release); $version_control->post_download($request); // Print release notes if --notes option is set. if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) { release_info_print_releasenotes(array($name . '-' . $release['version']), FALSE); } // Inform the user about available modules a/o themes in the downloaded project. drush_pm_extensions_in_project($request); } else { // We don't `return` here in order to proceed with downloading additional projects. drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location']))); } } } /** * Implementation of hook_drush_pm_download_destination_alter(). * * Built-in download-destination-alter hook. This particular version of * the hook will move modules that contain only drush commands to * /usr/share/drush/commands if it exists, or $HOME/.drush if the * site-wide location does not exist. */ function pm_drush_pm_download_destination_alter(&$project, $release) { // A module is a pure drush command if it has no .module and contain // .drush.inc files. Skip this test for drush itself, though; we do // not want to download drush to the ~/.drush folder. if (($project['project_type'] == 'module') && ($project['name'] != 'drush')) { $drush_command_files = drush_scan_directory($project['full_project_path'], '/.*\.drush.inc/'); if (!empty($drush_command_files)) { $module_files = drush_scan_directory($project['full_project_path'], '/.*\.module/'); if (empty($module_files)) { $install_dir = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES'); if (!is_dir($install_dir) || !is_writable($install_dir)) { $install_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION'); } // Make the .drush dir if it does not already exist. if (!is_dir($install_dir)) { drush_mkdir($install_dir, FALSE); } // Change the location if the mkdir worked. if (is_dir($install_dir)) { $project['project_install_location'] = $install_dir; } } // We need to clear the drush commandfile cache so that // our newly-downloaded drush extension commandfiles can be found. drush_cache_clear_all(); } } } /** * Determine a candidate destination directory for a particular site path and * return it if it exists, optionally attempting to create the directory. */ function _pm_download_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) { switch ($type) { case 'module': // Prefer sites/all/modules/contrib if it exists. $destination = $sitepath . '/modules'; $contrib = $destination . '/contrib'; if (is_dir($contrib)) { $destination = $contrib; } break; case 'theme': $destination = $sitepath . '/themes'; break; case 'theme engine': $destination = $sitepath . '/themes/engines'; break; case 'profile': $destination = $drupal_root . '/profiles'; break; } if ($create) { drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination))); drush_mkdir($destination, TRUE); } if (is_dir($destination)) { drush_log(dt('Using destination directory !dir', array('!dir' => $destination))); return $destination; } drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination))); return FALSE; } /** * Returns the best destination for a particular download type we can find. * * It is based on the project type and drupal and site contexts. */ function _pm_download_destination($type) { $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); $site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT', FALSE); $full_site_root = $drupal_root .'/'. $site_root; $sites_all = $drupal_root . '/sites/all'; $in_site_directory = FALSE; // Check if we are running within the site directory. if (strpos(realpath(drush_cwd()), realpath($full_site_root)) !== FALSE || (drush_get_option('use-site-dir', FALSE))) { $in_site_directory = TRUE; } $destination = ''; if ($type != 'core') { // Attempt 1: If we are in a specific site directory, and the destination // directory already exists, then we use that. if (empty($destination) && $site_root && $in_site_directory) { $create_dir = drush_get_option('use-site-dir', FALSE); $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, $create_dir); } // Attempt 2: If the destination directory already exists for sites/all, // then we use that. if (empty($destination) && $drupal_root) { $destination = _pm_download_destination_lookup($type, $drupal_root, $sites_all); } // Attempt 3: If a specific (non default) site directory exists and // sites/all does not exist, then we create destination in the site // specific directory. if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sites_all)) { $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE); } // Attempt 4: If sites/all exists, then we create destination in the // sites/all directory. if (empty($destination) && is_dir($sites_all)) { $destination = _pm_download_destination_lookup($type, $drupal_root, $sites_all, TRUE); } // Attempt 5: If site directory exists (even default), then we create // destination in the this directory. if (empty($destination) && $site_root && is_dir($full_site_root)) { $destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE); } } // Attempt 6: If we didn't find a valid directory yet (or we somehow found // one that doesn't exist) we always fall back to the current directory. if (empty($destination) || !is_dir($destination)) { $destination = drush_cwd(); } return $destination; } drush-5.10.0/commands/pm/info.pm.inc000066400000000000000000000072571222105546100172110ustar00rootroot00000000000000 $extension)), 'warning'); continue; } if ($info->type == 'module') { $data = _drush_pm_info_module($info); } else { $data = _drush_pm_info_theme($info); } drush_print_table(drush_key_value_to_array_table($data)); print "\n"; } } /** * Return an array with general info of an extension. */ function _drush_pm_info_extension($info) { $major_version = drush_drupal_major_version(); $data['Extension'] = $info->name; $data['Project'] = isset($info->info['project'])?$info->info['project']:dt('Unknown'); $data['Type'] = $info->type; $data['Title'] = $info->info['name']; $data['Description'] = $info->info['description']; $data['Version'] = $info->info['version']; $data['Date'] = format_date($info->info['datestamp'], 'custom', 'Y-m-d'); $data['Package'] = $info->info['package']; $data['Core'] = $info->info['core']; $data['PHP'] = $info->info['php']; $data['Status'] = drush_get_extension_status($info); $path = (($info->type == 'module')&&($major_version == 7))?$info->uri:$info->filename; $path = substr($path, 0, strrpos($path, '/')); $data['Path'] = $path; return $data; } /** * Return an array with info of a module. */ function _drush_pm_info_module($info) { $major_version = drush_drupal_major_version(); $data = _drush_pm_info_extension($info); if ($info->schema_version > 0) { $schema_version = $info->schema_version; } elseif ($info->schema_version == -1) { $schema_version = "no schema installed"; } else { $schema_version = "module has no schema"; } $data['Schema version'] = $schema_version; if ($major_version == 7) { $data['Files'] = implode(', ', $info->info['files']); } if (count($info->info['dependencies']) > 0) { $requires = implode(', ', $info->info['dependencies']); } else { $requires = "none"; } $data['Requires'] = $requires; if ($major_version == 6) { $requiredby = !empty($info->info['dependents'])?$info->info['dependents']:array("none"); } else { $requiredby = !empty($info->required_by)?array_keys($info->required_by):array("none"); } $data['Required by'] = implode(', ', $requiredby); return $data; } /** * Return an array with info of a theme. */ function _drush_pm_info_theme($info) { $major_version = drush_drupal_major_version(); $data = _drush_pm_info_extension($info); $data['Core'] = $info->info['core']; $data['PHP'] = $info->info['php']; $data['Engine'] = $info->info['engine']; $data['Base theme'] = isset($info->base_themes) ? implode($info->base_themes, ', ') : ''; $regions = implode(', ', $info->info['regions']); $data['Regions'] = $regions; $features = implode(', ', $info->info['features']); $data['Features'] = $features; if (count($info->info['stylesheets']) > 0) { $data['Stylesheets'] = ''; foreach ($info->info['stylesheets'] as $media => $files) { $files = implode(', ', array_keys($files)); $data['Media '.$media] = $files; } } if (count($info->info['scripts']) > 0) { $scripts = implode(', ', array_keys($info->info['scripts'])); $data['Scripts'] = $scripts; } return $data; } drush-5.10.0/commands/pm/package_handler/000077500000000000000000000000001222105546100202255ustar00rootroot00000000000000drush-5.10.0/commands/pm/package_handler/git_drupalorg.inc000066400000000000000000000254601222105546100235710ustar00rootroot00000000000000=1.7 // (avoid drush_shell_exec because we want to run this even in --simulated mode.) $success = exec('git --version', $git); $git_version_array = explode(" ", $git[0]); $git_version = $git_version_array[2]; drush_set_context('DRUSH_DEBUG', $debug); if (!$success) { return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.')); } elseif ($git_version < '1.7') { return drush_set_error('GIT_VERSION_UNSUPPORTED', dt('Your git version !git_version is not supported; please upgrade to git 1.7 or later.', array('!git_version' => $git_version))); } // Check git_deploy is enabled. Only for bootstrapped sites. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { if (!drush_get_option('gitinfofile') && !module_exists('git_deploy')) { drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), 'warning'); } } return TRUE; } /** * Download a project. * * @param $request * The project array with name, base and full (final) paths. * @param $release * The release details array from drupal.org. */ function package_handler_download_project(&$request, $release) { if ($username = drush_get_option('gitusername')) { // Uses SSH, which enables pushing changes back to git.drupal.org. $repository = $username . '@git.drupal.org:project/' . $request['name'] . '.git'; } else { $repository = 'git://git.drupal.org/project/' . $request['name'] . '.git'; } $request['repository'] = $repository; $tag = $release['tag']; // If the --cache option was given, create a new git reference cache of the // remote repository, or update the existing cache to fetch recent changes. if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) { $gitcache = $cachedir . '/git'; $projectcache = $gitcache . '/' . $request['name'] . '.git'; drush_mkdir($gitcache); // Setup a new cache, if we don't have this project yet. if (!file_exists($projectcache)) { // --mirror works similar to --bare, but retrieves all tags, local // branches, remote branches, and any other refs (notes, stashes, etc). // @see http://stackoverflow.com/questions/3959924 $command = 'git clone --mirror'; if (drush_get_context('DRUSH_VERBOSE')) { $command .= ' --verbose --progress'; } $command .= ' %s %s'; drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git'); } // If we already have this project, update it to speed up subsequent clones. else { // A --mirror clone is fully synchronized with `git remote update` instead // of `git fetch --all`. // @see http://stackoverflow.com/questions/6150188 drush_shell_cd_and_exec($projectcache, 'git remote update'); } $gitcache = $projectcache; } // Clone the repo into its appropriate target location. $command = 'git clone'; $command .= ' ' . drush_get_option('gitcloneparams'); if (drush_get_option('cache')) { $command .= ' --reference ' . drush_escapeshellarg($gitcache); } if (drush_get_context('DRUSH_VERBOSE')) { $command .= ' --verbose --progress'; } $command .= ' ' . drush_escapeshellarg($repository); $command .= ' ' . drush_escapeshellarg($request['full_project_path']); if (!drush_shell_exec($command)) { return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name']))); } // Check if the 'tag' from the release feed is a tag or a branch. // If the tag exists, git will return it if (!drush_shell_cd_and_exec($request['full_project_path'], 'git tag -l ' . drush_escapeshellarg($tag))) { return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name']))); } $output = drush_shell_exec_output(); if (isset($output[0]) && ($output[0] == $tag)) { // If we want a tag, simply checkout it. The checkout will end up in // "detached head" state. $command = 'git checkout ' . drush_get_option('gitcheckoutparams'); $command .= ' ' . drush_escapeshellarg($tag); if (!drush_shell_cd_and_exec($request['full_project_path'], $command)) { return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.'); } } else { // Else, we want to checkout a branch. // First check if we are not already in the correct branch. if (!drush_shell_cd_and_exec($request['full_project_path'], 'git symbolic-ref HEAD')) { return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.'); } $output = drush_shell_exec_output(); $current_branch = preg_replace('@^refs/heads/@', '', $output[0]); // If we are not on the correct branch already, switch to the correct one. if ($current_branch != $tag) { $command = 'git checkout'; $command .= ' ' . drush_get_option('gitcheckoutparams'); $command .= ' --track ' . drush_escapeshellarg('origin/' . $tag) . ' -b ' . drush_escapeshellarg($tag); if (!drush_shell_cd_and_exec($request['full_project_path'], $command)) { return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.'); } } } return TRUE; } /** * Update a project (so far, only modules are supported). * * @param $request * The project array with name, base and full (final) paths. * @param $release * The release details array from drupal.org. */ function package_handler_update_project($request, $release) { drush_log('Updating project ' . $request['name'] . ' ...'); $commands = array(); if ($release['version_extra'] == 'dev') { // Update the branch of the development repository. $commands[] = 'git pull'; $commands[] = drush_get_option('gitpullparams'); } else { // Use a stable repository. $commands[] = 'git fetch'; $commands[] = drush_get_option('gitfetchparams'); $commands[] = ';'; $commands[] = 'git checkout'; $commands[] = drush_get_option('gitcheckoutparams'); $commands[] = $release['version']; } if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) { return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupal.org.'); } return TRUE; } /** * Post download action. * * This action take place once the project is placed in its final location. * * Here we add the project as a git submodule. */ function package_handler_post_download($project, $release) { if (drush_get_option('gitsubmodule', FALSE)) { // Obtain the superproject path, then add as submodule. if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) { $output = drush_shell_exec_output(); $superproject = $output[0]; // Add the downloaded project as a submodule of its git superproject. $command = array(); $command[] = 'git submodule add'; $command[] = drush_get_option('gitsubmoduleaddparams'); $command[] = $project['repository']; // We need the submodule relative path. $command[] = substr($project['full_project_path'], strlen($superproject) + 1); if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) { return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject))); } } else { return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path'])))); } } if (drush_get_option('gitinfofile', FALSE)) { $matches = array(); if (preg_match('/^(.+).x-dev$/', $release['version'], $matches)) { $full_version = drush_pm_git_drupalorg_compute_rebuild_version($project['project_install_location'], $matches[1]); } else { $full_version = $release['version']; } drush_pm_inject_info_file_metadata($project['project_install_location'], $project['name'], $full_version); } } /** * Helper function to compute the rebulid version string for a project. * * This does some magic in Git to find the latest release tag along * the branch we're packaging from, count the number of commits since * then, and use that to construct this fancy alternate version string * which is useful for the version-specific dependency support in Drupal * 7 and higher. * * NOTE: A similar function lives in git_deploy and in the drupal.org * packaging script (see DrupalorgProjectPackageRelease.class.php inside * drupalorg/drupalorg_project/plugins/release_packager). Any changes to the * actual logic in here should probably be reflected in the other places. * * @param string $project_dir * The full path to the root directory of the project to operate on. * @param string $branch * The branch that we're using for -dev. This should only include the * core version, the dash, and the branch's major version (eg. '7.x-2'). * * @return string * The full 'rebuild version string' in the given Git checkout. */ function drush_pm_git_drupalorg_compute_rebuild_version($project_dir, $branch) { $rebuild_version = ''; $branch_preg = preg_quote($branch); if (drush_shell_cd_and_exec($project_dir, 'git describe --tags')) { $shell_output = drush_shell_exec_output(); $last_tag = $shell_output[0]; // Make sure the tag starts as Drupal formatted (for eg. // 7.x-1.0-alpha1) and if we are on a proper branch (ie. not master) // then it's on that branch. if (preg_match('/^(?' . $branch_preg . '\.\d+(?:-[^-]+)?)(?-(?\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) { // If we found additional git metadata (in particular, number of commits) // then use that info to build the version string. if (isset($matches['gitextra'])) { $rebuild_version = $matches['drupalversion'] . '+' . $matches['numberofcommits'] . 'dev'; } // Otherwise, the branch tip is pointing to the same commit as the // last tag on the branch, in which case we use the prior tag and // add '+0-dev' to indicate we're still on a -dev branch. else { $rebuild_version = $last_tag . '+0-dev'; } } } return $rebuild_version; } drush-5.10.0/commands/pm/package_handler/wget.inc000066400000000000000000000074551222105546100217010ustar00rootroot00000000000000 $file) { if ((string)$file->variant == drush_get_option('variant', 'full') && (string)$file->archive_type == 'tar.gz') { $release['download_link'] = (string)$file->url; $release['mdhash'] = (string)$file->md5; $release['date'] = (string)$file->filedate; break; } } } } // Add to URL so it is part of the cache key . Dev snapshots can then be cached forever. if (strpos($release['download_link'], '-dev') !== FALSE) { $release['download_link'] .= '?date=' . $release['date']; } $filename = basename($release['download_link']); // On Windows file name cannot contain '?' // See http://drupal.org/node/1782444 if(drush_is_windows()) { $filename = str_replace('?', '_', $filename); } $cache_duration = 86400*365; $path = drush_download_file($release['download_link'], $request['base_project_path'] . DIRECTORY_SEPARATOR . $filename, $cache_duration); if ($path || drush_get_context('DRUSH_SIMULATE')) { drush_log("Downloading " . $filename . " was successful."); } else { return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $request['base_project_path'] . ' from '. $release['download_link']); } // Check Md5 hash. if (drush_op('md5_file', $path) != $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) { drush_set_error('DRUSH_PM_FILE_CORRUPT', "File $filename is corrupt (wrong md5 checksum)."); return FALSE; } else { drush_log("Md5 checksum of $filename verified."); } // Extract the tarball. $file_list = drush_tarball_extract($path, $request['base_project_path'], TRUE); // Move untarred directory to project_dir, if distinct. if (($request['project_type'] == 'core') || (($request['project_type'] == 'profile') && (drush_get_option('variant', 'full') == 'full'))) { // Obtain the dodgy project_dir for drupal core. $project_dir = drush_trim_path($file_list[0]); if ($request['project_dir'] != $project_dir) { $path = $request['base_project_path']; drush_move_dir($path . '/'. $project_dir, $path . '/' . $request['project_dir']); } } return TRUE; } /** * This is an alias of the download function, since they are identical */ function package_handler_update_project(&$request, $release) { return package_handler_download_project($request, $release); } /** * Post download action. * * This action take place once the project is placed in its final location. */ function package_handler_post_download($project) { } drush-5.10.0/commands/pm/pm.drush.inc000066400000000000000000002105451222105546100173770ustar00rootroot00000000000000 drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES'))); } } /** * Implementation of hook_drush_command(). */ function pm_drush_command() { $update = 'update'; $update_options = array( 'security-only' => 'Only update modules that have security updates available. However, if there were other releases of a module between the installed version the security update, other changes to features or functionality may occur.', 'lock' => array( 'description' => 'Add a persistent lock to remove the specified projects from consideration during updates. Locks may be removed with the --unlock parameter, or overridden by specifically naming the project as a parameter to pm-update or pm-updatecode. The lock does not affect pm-download. See also the update_advanced project for similar and improved functionality.', 'example-value' => 'foo,bar', ), ); $update_suboptions = array( 'lock' => array( 'lock-message' => array( 'description' => 'A brief message explaining why a project is being locked; displayed during pm-updatecode. Optional.', 'example-value' => 'message', ), 'unlock' => array( 'description' => 'Remove the persistent lock from the specified projects so that they may be updated again.', 'example-value' => 'foo,bar', ), ), ); $items['pm-enable'] = array( 'description' => 'Enable one or more extensions (modules or themes).', 'arguments' => array( 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to enable all matches.', ), 'options' => array( 'resolve-dependencies' => 'Attempt to download any missing dependencies. At the moment, only works when the module name is the same as the project name.', 'skip' => 'Skip automatic downloading of libraries (c.f. devel).', ), 'aliases' => array('en'), ); $items['pm-disable'] = array( 'description' => 'Disable one or more extensions (modules or themes).', 'arguments' => array( 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to disable multiple matches.', ), 'aliases' => array('dis'), ); $items['pm-info'] = array( 'description' => 'Show detailed info for one or more extensions (modules or themes).', 'arguments' => array( 'extensions' => 'A list of modules or themes. You can use the * wildcard at the end of extension names to show info for multiple matches. If no argument is provided it will show info for all available extensions.', ), 'aliases' => array('pmi'), ); // Install command is reserved for the download and enable of projects including dependencies. // @see http://drupal.org/node/112692 for more information. // $items['install'] = array( // 'description' => 'Download and enable one or more modules', // ); $items['pm-uninstall'] = array( 'description' => 'Uninstall one or more modules.', 'arguments' => array( 'modules' => 'A list of modules.', ), ); $items['pm-list'] = array( 'description' => 'Show a list of available extensions (modules and themes).', 'callback arguments' => array(array(), FALSE), 'options' => array( 'type' => array( 'description' => 'Filter by extension type. Choices: module, theme.', 'example-value' => 'module', ), 'status' => array( 'description' => 'Filter by extension status. Choices: enabled, disabled and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").', 'example-value' => 'disabled', ), 'package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").', 'core' => 'Filter out extensions that are not in drupal core.', 'no-core' => 'Filter out extensions that are provided by drupal core.', 'pipe' => 'Returns a whitespace delimited list of the names of the resulting extensions.', ), 'aliases' => array('pml'), ); $items['pm-refresh'] = array( 'description' => 'Refresh update status information.', 'drupal dependencies' => array($update), 'aliases' => array('rf'), ); $items['pm-updatecode'] = array( 'description' => 'Update Drupal core and contrib projects to latest recommended releases.', 'drupal dependencies' => array($update), 'arguments' => array( 'projects' => 'Optional. A list of installed projects to update.', ), 'options' => array( 'pipe' => 'Returns a whitespace delimited list of projects with any of its extensions enabled and their respective version and update information, one project per line. Order: project name, current version, recommended version, update status.', 'notes' => 'Show release notes for each project to be updated.', 'no-core' => 'Only update modules and skip the core update.', 'check-updatedb' => 'Check to see if an updatedb is needed after updating the code. Default is on; use --check-updatedb=0 to disable.', ) + $update_options, 'sub-options' => $update_suboptions, 'aliases' => array('upc'), 'topics' => array('docs-policy'), 'engines' => array( 'version_control', 'package_handler', 'release_info' => array( 'add-options-to-command' => FALSE, ), ), ); // Merge all items from above. $items['pm-update'] = array( 'description' => 'Update Drupal core and contrib projects and apply any pending database updates (Same as pm-updatecode + updatedb).', 'drupal dependencies' => array($update), 'aliases' => array('up'), 'allow-additional-options' => array('pm-updatecode', 'updatedb'), ); $items['pm-updatecode-postupdate'] = array( 'description' => 'Notify of pending db updates.', 'hidden' => TRUE, ); $items['pm-releasenotes'] = array( 'description' => 'Print release notes for given projects.', 'arguments' => array( 'projects' => 'A list of project names, with optional version. Defaults to \'drupal\'', ), 'options' => array( 'html' => dt('Display releasenotes in HTML rather than plain text.'), ), 'examples' => array( 'drush rln cck' => 'Prints the release notes for the recommended version of CCK project.', 'drush rln token-1.13' => 'View release notes of a specfic version of the Token project for my version of Drupal.', 'drush rln pathauto zen' => 'View release notes for the recommended version of Pathauto and Zen projects.', ), 'aliases' => array('rln'), 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'engines' => array( 'release_info', ), ); $items['pm-releases'] = array( 'description' => 'Print release information for given projects.', 'arguments' => array( 'projects' => 'A list of drupal.org project names. Defaults to \'drupal\'', ), 'examples' => array( 'drush pm-releases cck zen' => 'View releases for cck and Zen projects for your Drupal version.', ), 'aliases' => array('rl'), 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'engines' => array( 'release_info', ), ); $items['pm-download'] = array( 'description' => 'Download projects from drupal.org or other sources.', 'examples' => array( 'drush dl drupal' => 'Download latest recommended release of Drupal core.', 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core.', 'drush dl drupal-6' => 'Download latest recommended release of Drupal 6.x.', 'drush dl cck zen' => 'Download latest versions of CCK and Zen projects.', 'drush dl og-1.3' => 'Download a specfic version of Organic groups module for my version of Drupal.', 'drush dl diff-6.x-2.x' => 'Download a specific development branch of diff module for a specific Drupal version.', 'drush dl views --select' => 'Show a list of recent releases of the views project, prompt for which one to download.', 'drush dl webform --dev' => 'Download the latest dev release of webform.', 'drush dl webform --cache' => 'Download webform. Fetch and populate the download cache as needed.', ), 'arguments' => array( 'projects' => 'A comma delimited list of drupal.org project names, with optional version. Defaults to \'drupal\'', ), 'options' => array( 'destination' => array( 'description' => 'Path to which the project will be copied. If you\'re providing a relative path, note it is relative to the drupal root (if bootstrapped).', 'example-value' => 'path', ), 'use-site-dir' => 'Force to use the site specific directory. It will create the directory if it doesn\'t exist. If --destination is also present this option will be ignored.', 'notes' => 'Show release notes after each project is downloaded.', 'variant' => array( 'description' => "Only useful for install profiles. Possible values: 'full', 'projects', 'profile-only'.", 'example-value' => 'full', ), 'select' => "Select the version to download interactively from a list of available releases.", 'drupal-project-rename' => 'Alternate name for "drupal-x.y" directory when downloading Drupal project. Defaults to "drupal".', 'default-major' => array( 'description' => 'Specify the default major version of modules to download when there is no bootstrapped Drupal site. Defaults to "7".', 'example-value' => '6', ), 'skip' => 'Skip automatic downloading of libraries (c.f. devel).', 'pipe' => 'Returns a list of the names of the extensions (modules and themes) contained in the downloaded projects.', ), 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'aliases' => array('dl'), 'engines' => array( 'version_control', 'package_handler', 'release_info', ), ); return $items; } /** * @defgroup extensions Extensions management. * @{ * Functions to manage extensions. */ /** * Command argument complete callback. */ function pm_pm_enable_complete() { return pm_complete_extensions(); } /** * Command argument complete callback. */ function pm_pm_disable_complete() { return pm_complete_extensions(); } /** * Command argument complete callback. */ function pm_pm_uninstall_complete() { return pm_complete_extensions(); } /** * Command argument complete callback. */ function pm_pm_info_complete() { return pm_complete_extensions(); } /** * Command argument complete callback. */ function pm_pm_releasenotes_complete() { return pm_complete_projects(); } /** * Command argument complete callback. */ function pm_pm_releases_complete() { return pm_complete_projects(); } /** * Command argument complete callback. */ function pm_pm_updatecode_complete() { return pm_complete_projects(); } /** * Command argument complete callback. */ function pm_pm_update_complete() { return pm_complete_projects(); } /** * List extensions for completion. * * @return * Array of available extensions. */ function pm_complete_extensions() { if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { $extension_info = drush_get_extensions(FALSE); return array('values' => array_keys($extension_info)); } } /** * List projects for completion. * * @return * Array of installed projects. */ function pm_complete_projects() { if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { return array('values' => array_keys(drush_get_projects())); } } /** * Sort callback function for sorting extensions. * * It will sort first by type, second by package and third by name. */ function _drush_pm_sort_extensions($a, $b) { if ($a->type == 'module' && $b->type == 'theme') { return -1; } if ($a->type == 'theme' && $b->type == 'module') { return 1; } $cmp = strcasecmp($a->info['package'], $b->info['package']); if ($cmp == 0) { $cmp = strcasecmp($a->info['name'], $b->info['name']); } return $cmp; } /** * Calculate a extension status based on current status and schema version. * * @param $extension * Object of a single extension info. * * @return * String describing extension status. Values: enabled|disabled|not installed */ function drush_get_extension_status($extension) { if (($extension->type == 'module')&&($extension->schema_version == -1)) { $status = "not installed"; } else { $status = ($extension->status == 1)?'enabled':'disabled'; } return $status; } /** * Classify extensions as modules, themes or unknown. * * @param $extensions * Array of extension names, by reference. * @param $modules * Empty array to be filled with modules in the provided extension list. * @param $themes * Empty array to be filled with themes in the provided extension list. */ function drush_pm_classify_extensions(&$extensions, &$modules, &$themes, $extension_info) { _drush_pm_expand_extensions($extensions, $extension_info); foreach ($extensions as $extension) { if (!isset($extension_info[$extension])) { continue; } if ($extension_info[$extension]->type == 'module') { $modules[$extension] = $extension; } else if ($extension_info[$extension]->type == 'theme') { $themes[$extension] = $extension; } } } /** * Obtain an array of installed projects off the extensions available. * * A project is considered to be 'enabled' when any of its extensions is * enabled. * If any extension lacks project information and it is found that the * extension was obtained from drupal.org's cvs or git repositories, a new * 'vcs' attribute will be set on the extension. Example: * $extensions[name]->vcs = 'cvs'; * * @param array $extensions * Array of extensions as returned by drush_get_extensions(). * * @return * Array of installed projects with info of version, status and provided * extensions. */ function drush_get_projects(&$extensions = NULL) { if (is_null($extensions)) { $extensions = drush_get_extensions(); } $projects = array( 'drupal' => array( 'label' => 'Drupal', 'version' => VERSION, 'type' => 'core', 'extensions' => array(), ) ); foreach ($extensions as $extension) { // The project name is not available in this cases: // 1. the extension is part of drupal core. // 2. the project was checked out from CVS/git and cvs_deploy/git_deploy // is not installed. // 3. it is not a project hosted in drupal.org. if (empty($extension->info['project'])) { if (isset($extension->info['version']) && ($extension->info['version'] == VERSION)) { $project = 'drupal'; } else { if (is_dir(dirname($extension->filename) . '/CVS') && (!module_exists('cvs_deploy'))) { $extension->vcs = 'cvs'; drush_log(dt('Extension !extension is fetched from cvs. Ignoring.', array('!extension' => $extension->name)), 'debug'); } elseif (is_dir(dirname($extension->filename) . '/.git') && (!module_exists('git_deploy'))) { $extension->vcs = 'git'; drush_log(dt('Extension !extension is fetched from git. Ignoring.', array('!extension' => $extension->name)), 'debug'); } continue; } } else { $project = $extension->info['project']; } // Create/update the project in $projects with the project data. if (!isset($projects[$project])) { $projects[$project] = array( // If there's an extension with matching name, pick its label. // Otherwise use just the project name. We avoid $extension->label // for the project label because the extension's label may have // no direct relation with the project name. For example, // "Text (text)" or "Number (number)" for the CCK project. 'label' => isset($extensions[$project])?$extensions[$project]->label:$project, 'type' => $extension->type, 'version' => $extension->info['version'], 'status' => $extension->status, 'extensions' => array(), ); if (isset($extension->info['project status url'])) { $projects[$project]['status url'] = $extension->info['project status url']; } } elseif ($extension->status != 0) { $projects[$project]['status'] = $extension->status; } $projects[$project]['extensions'][] = $extension->name; } // Obtain each project's path and try to provide a better label for ones // with machine name. $reserved = array('modules', 'sites', 'themes'); foreach ($projects as $name => $project) { if ($name == 'drupal') { continue; } // If this project has no human label and it only contains an extension, // construct a label based on the extension name. if (($project['label'] == $name) && (count($project['extensions']) == 1)) { $extension = $extensions[$project['extensions'][0]]; $projects[$name]['label'] = $extension->info['name'] . ' (' . $name . ')'; } drush_log(dt('Obtaining !project project path.', array('!project' => $name)), 'debug'); $path = _drush_pm_find_common_path($project['type'], $project['extensions']); // Prevent from setting a reserved path. For example it may happen in a case // where a module and a theme are declared as part of a same project. // There's a special case, a project called "sites", this is the reason for // the second condition here. if ((in_array(basename($path), $reserved)) && (!in_array($name, $reserved))) { drush_log(dt('Error while trying to find the common path for enabled extensions of project !project. Extensions are: !extensions.', array('!project' => $name, '!extensions' => implode(', ', $project['extensions']))), 'error'); } else { $projects[$name]['path'] = $path; } } return $projects; } /** * Helper function to find the common path for a list of extensions in the aim to obtain the project name. * * @param $project_type * Type of project we're trying to find. Valid values: module, theme. * @param $extensions * Array of extension names. */ function _drush_pm_find_common_path($project_type, $extensions) { // Select the first path as the candidate to be the common prefix. $extension = array_pop($extensions); while (!($path = drupal_get_path($project_type, $extension))) { drush_log(dt('Unknown path for !extension !type.', array('!extension' => $extension, '!type' => $project_type)), 'warning'); $extension = array_pop($extensions); } // If there's only one extension we are done. Otherwise, we need to find // the common prefix for all of them. if (count($extensions) > 0) { // Iterate over the other projects. while($extension = array_pop($extensions)) { $path2 = drupal_get_path($project_type, $extension); if (!$path2) { drush_log(dt('Unknown path for !extension !type.', array('!extension' => $extension, '!type' => $project_type)), 'debug'); continue; } // Option 1: same path. if ($path == $path2) { continue; } // Option 2: $path is a prefix of $path2. if (strpos($path2, $path) === 0) { continue; } // Option 3: $path2 is a prefix of $path. if (strpos($path, $path2) === 0) { $path = $path2; continue; } // Option 4: no one is a prefix of the other. Find the common // prefix by iteratively strip the rigthtmost piece of $path. // We will iterate until a prefix is found or path = '.', that on the // other hand is a condition theorically impossible to reach. do { $path = dirname($path); if (strpos($path2, $path) === 0) { break; } } while ($path != '.'); } } return $path; } /** * Returns a list of enabled modules. * * This is a simplified version of module_list(). */ function pm_module_list() { $enabled = array(); $rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1)); while ($row = drush_db_result($rsc)) { $enabled[$row] = $row; } return $enabled; } /** * @} End of "defgroup extensions". */ /** * Command callback. Show a list of extensions with type and status. */ function drush_pm_list() { //--package $package_filter = array(); $package = strtolower(drush_get_option('package')); if (!empty($package)) { $package_filter = explode(',', $package); } if (empty($package_filter) || count($package_filter) > 1) { $header[] = dt('Package'); } $header[] = dt('Name'); //--type $all_types = array('module', 'theme'); $type_filter = strtolower(drush_get_option('type')); if (!empty($type_filter)) { $type_filter = explode(',', $type_filter); } else { $type_filter = $all_types; } if (count($type_filter) > 1) { $header[] = dt('Type'); } foreach ($type_filter as $type) { if (!in_array($type, $all_types)) { //TODO: this kind of chck can be implemented drush-wide return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!type is not a valid project type.', array('!type' => $type))); } } //--status $all_status = array('enabled', 'disabled', 'not installed'); $status_filter = strtolower(drush_get_option('status')); if (!empty($status_filter)) { $status_filter = explode(',', $status_filter); } else { $status_filter = $all_status; } if (count($status_filter) > 1) { $header[] = dt('Status'); } foreach ($status_filter as $status) { if (!in_array($status, $status_filter)) { //TODO: this kind of chck can be implemented drush-wide return drush_set_error('DRUSH_PM_INVALID_PROJECT_TYPE', dt('!status is not a valid project status.', array('!status' => $status))); } } $header[] = dt('Version'); $rows[] = $header; $extension_info = drush_get_extensions(FALSE); uasort($extension_info, '_drush_pm_sort_extensions'); $major_version = drush_drupal_major_version(); foreach ($extension_info as $key => $extension) { if (!in_array($extension->type, $type_filter)) { unset($extension_info[$key]); continue; } $status = drush_get_extension_status($extension); if (!in_array($status, $status_filter)) { unset($extension_info[$key]); continue; } // filter out core if --no-core specified if (drush_get_option('no-core', FALSE)) { if (($extension->info['package'] == 'Core') || ((array_key_exists('project', $extension->info)) && ($extension->info['project'] == 'drupal'))) { unset($extension_info[$key]); continue; } } // filter out non-core if --core specified if (drush_get_option('core', FALSE)) { if (($extension->info['package'] != 'Core') && ((!array_key_exists('project', $extension->info)) || ($extension->info['project'] != 'drupal'))) { unset($extension_info[$key]); continue; } } // filter by package if (!empty($package_filter)) { if (!in_array(strtolower($extension->info['package']), $package_filter)) { unset($extension_info[$key]); continue; } } if (empty($package_filter) || count($package_filter) > 1) { $row[] = $extension->info['package']; } $row[] = $extension->label; if (count($type_filter) > 1) { $row[] = ucfirst($extension->type); } if (count($status_filter) > 1) { $row[] = ucfirst($status); } if (($major_version >= 6)||($extension->type == 'module')) { // Suppress notice when version is not present. $row[] = @$extension->info['version']; } $rows[] = $row; $pipe[] = $extension->name; unset($row); } drush_print_table($rows, TRUE); if (isset($pipe)) { // Newline-delimited list for use by other scripts. Set the --pipe option. drush_print_pipe($pipe); } // Return the result for backend invoke return $extension_info; } /** * Helper function for pm-enable. */ function drush_pm_find_project_from_extension($extension) { $result = drush_pm_lookup_extension_in_cache($extension); if (!isset($result)) { drush_include_engine('release_info', 'updatexml'); // If we can find info on a project that has the same name // as the requested extension, then we'll call that a match. $request = pm_parse_project_version(array($extension)); if (release_info_check_project($request[$extension])) { $result = $extension; } } return $result; } /** * Validate callback. Determine the modules and themes that the user would like enabled. */ function drush_pm_enable_validate() { $args = pm_parse_arguments(func_get_args()); $extension_info = drush_get_extensions(); $recheck = TRUE; while ($recheck) { $recheck = FALSE; // Classify $args in themes, modules or unknown. $modules = array(); $themes = array(); $download = array(); drush_pm_classify_extensions($args, $modules, $themes, $extension_info); $extensions = array_merge($modules, $themes); $unknown = array_diff($args, $extensions); // If there're unknown extensions, try and download projects // with matching names. if (!empty($unknown)) { $found = array(); foreach ($unknown as $key => $name) { drush_log(dt('!extension was not found.', array('!extension' => $name)), 'warning'); $project = drush_pm_find_project_from_extension($name); if (!empty($project)) { $found[] = $project; } } if (!empty($found)) { drush_log(dt("The following projects provide some or all of the extensions not found:\n@list", array('@list' => implode("\n", $found))), 'ok'); if (drush_get_option('resolve-dependencies')) { drush_log(dt("They are being downloaded."), 'ok'); } if ((drush_get_option('resolve-dependencies')) || (drush_confirm("Would you like to download them?"))) { $download = $found; } } } // Discard already enabled and incompatible extensions. foreach ($extensions as $name) { if ($extension_info[$name]->status) { drush_log(dt('!extension is already enabled.', array('!extension' => $name)), 'ok'); } // Check if the extension is compatible with Drupal core and php version. if ($component = drush_extension_check_incompatibility($extension_info[$name])) { drush_set_error('DRUSH_PM_ENABLE_MODULE_INCOMPATIBLE', dt('!name is incompatible with the !component version.', array('!name' => $name, '!component' => $component))); if ($extension_info[$name]->type == 'module') { unset($modules[$name]); } else { unset($themes[$name]); } } } if (!empty($modules)) { // Check module dependencies. $dependencies = drush_check_module_dependencies($modules, $extension_info); $unmet_dependencies = array(); foreach ($dependencies as $module => $info) { if (!empty($info['unmet-dependencies'])) { foreach ($info['unmet-dependencies'] as $unmet_module) { $unmet_project = drush_pm_find_project_from_extension($unmet_module); if (!empty($unmet_project)) { $unmet_dependencies[$module][$unmet_project] = $unmet_project; } } } } if (!empty($unmet_dependencies)) { $msgs = array(); $unmet_project_list = array(); foreach ($unmet_dependencies as $module => $unmet_projects) { $unmet_project_list = array_merge($unmet_project_list, $unmet_projects); $msgs[] = dt("!module requires !unmet-projects", array('!unmet-projects' => implode(', ', $unmet_projects), '!module' => $module)); } drush_log(dt("The following projects have unmet dependencies:\n!list", array('!list' => implode("\n", $msgs))), 'ok'); if (drush_get_option('resolve-dependencies')) { drush_log(dt("They are being downloaded."), 'ok'); } if (drush_get_option('resolve-dependencies') || drush_confirm(dt("Would you like to download them?"))) { $download = array_merge($download, $unmet_project_list); } } } if (!empty($download)) { // Disable DRUSH_AFFIRMATIVE context temporarily. $drush_affirmative = drush_get_context('DRUSH_AFFIRMATIVE'); drush_set_context('DRUSH_AFFIRMATIVE', FALSE); // Invoke a new process to download dependencies. $result = drush_invoke_process('@self', 'pm-download', $download, array(), array('interactive' => TRUE)); // Restore DRUSH_AFFIRMATIVE context. drush_set_context('DRUSH_AFFIRMATIVE', $drush_affirmative); // Refresh module cache after downloading the new modules. $extension_info = drush_get_extensions(); $recheck = TRUE; } } if (!empty($modules)) { $all_dependencies = array(); $dependencies_ok = TRUE; foreach ($dependencies as $key => $info) { if (isset($info['error'])) { unset($modules[$key]); $dependencies_ok = drush_set_error($info['error']['code'], $info['error']['message']); } elseif (!empty($info['dependencies'])) { // Make sure we have an assoc array. $assoc = drupal_map_assoc($info['dependencies']); $all_dependencies = array_merge($all_dependencies, $assoc); } } if (!$dependencies_ok) { return FALSE; } $modules = array_diff(array_merge($modules, $all_dependencies), pm_module_list()); // Discard modules which doesn't meet requirements. require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; foreach ($modules as $key => $module) { // Check to see if the module can be installed/enabled (hook_requirements). // See @system_modules_submit if (!drupal_check_module($module)) { unset($modules[$key]); drush_set_error('DRUSH_PM_ENABLE_MODULE_UNMEET_REQUIREMENTS', dt('Module !module doesn\'t meet the requirements to be enabled.', array('!module' => $module))); _drush_log_drupal_messages(); return FALSE; } } } $searchpath = array(); foreach (array_merge($modules, $themes) as $name) { $searchpath[] = dirname($extension_info[$name]->filename); } // Add all modules that passed validation to the drush // list of commandfiles (if they have any). This // will allow these newly-enabled modules to participate // in the pre-pm_enable and post-pm_enable hooks. if (!empty($searchpath)) { _drush_add_commandfiles($searchpath); } drush_set_context('PM_ENABLE_EXTENSION_INFO', $extension_info); drush_set_context('PM_ENABLE_MODULES', $modules); drush_set_context('PM_ENABLE_THEMES', $themes); return TRUE; } /** * Command callback. Enable one or more extensions from downloaded projects. * Note that the modules and themes to be enabled were evaluated during the * pm-enable validate hook, above. */ function drush_pm_enable() { // Get the data built during the validate phase $extension_info = drush_get_context('PM_ENABLE_EXTENSION_INFO'); $modules = drush_get_context('PM_ENABLE_MODULES'); $themes = drush_get_context('PM_ENABLE_THEMES'); // Inform the user which extensions will finally be enabled. $extensions = array_merge($modules, $themes); if (empty($extensions)) { return drush_log(dt('There were no extensions that could be enabled.'), 'ok'); } else { drush_print(dt('The following extensions will be enabled: !extensions', array('!extensions' => implode(', ', $extensions)))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } } // Enable themes. if (!empty($themes)) { drush_theme_enable($themes); } // Enable modules and pass dependency validation in form submit. if (!empty($modules)) { drush_module_enable($modules); } // Inform the user of final status. $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); $problem_extensions = array(); while ($extension = drush_db_fetch_object($rsc)) { if ($extension->status) { drush_log(dt('!extension was enabled successfully.', array('!extension' => $extension->name)), 'ok'); } else { $problem_extensions[] = $extension->name; } } if (!empty($problem_extensions)) { return drush_set_error('DRUSH_PM_ENABLE_EXTENSION_ISSUE', dt('There was a problem enabling !extension.', array('!extension' => implode(',', $problem_extensions)))); } // Return the list of extensions enabled return $extensions; } /** * Command callback. Disable one or more extensions. */ function drush_pm_disable() { $args = pm_parse_arguments(func_get_args()); $extension_info = drush_get_extensions(); // classify $args in themes, modules or unknown. $modules = array(); $themes = array(); drush_pm_classify_extensions($args, $modules, $themes, $extension_info); $extensions = array_merge($modules, $themes); $unknown = array_diff($args, $extensions); // Discard and set an error for each unknown extension. foreach ($unknown as $name) { drush_log('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be disabled.', array('!extension' => $name)), 'warning'); } // Discard already disabled extensions. foreach ($extensions as $name) { if (!$extension_info[$name]->status) { if ($extension_info[$name]->type == 'module') { unset($modules[$name]); } else { unset($themes[$name]); } drush_log(dt('!extension is already disabled.', array('!extension' => $name)), 'ok'); } } // Discard default theme. if (!empty($themes)) { $default_theme = drush_theme_get_default(); if (in_array($default_theme, $themes)) { unset($themes[$default_theme]); drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), 'ok'); } } if (!empty($modules)) { // Add enabled dependents to the list of modules to disable. $dependents = drush_module_dependents($modules, $extension_info); $dependents = array_intersect($dependents, pm_module_list()); $modules = array_merge($modules, $dependents); // Discard required modules. $required = drush_drupal_required_modules($extension_info); foreach ($required as $module) { if (isset($modules[$module])) { unset($modules[$module]); $info = $extension_info[$module]->info; // No message for hidden modules. if (!isset($info['hidden'])) { $explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation', array('!explanation' => strip_tags($info['explanation']))) : ''; drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)) . $explanation, 'ok'); } } } } // Inform the user which extensions will finally be disabled. $extensions = array_merge($modules, $themes); if (empty($extensions)) { return drush_log(dt('There were no extensions that could be disabled.'), 'ok'); } else { drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions)))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } } // Disable themes. if (!empty($themes)) { drush_theme_disable($themes); } // Disable modules and pass dependency validation in form submit. if (!empty($modules)) { drush_module_disable($modules); } // Inform the user of final status. $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions)); $problem_extensions = array(); while ($extension = drush_db_fetch_object($rsc)) { if (!$extension->status) { drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), 'ok'); } else { $problem_extensions[] = $extension->name; } } if (!empty($problem_extensions)) { return drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => implode(',', $problem_extensions)))); } } /** * Add extensions that match extension_name*. * * A helper function for commands that take a space separated list of extension * names. It will identify extensions that have been passed in with a * trailing * and add all matching extensions to the array that is returned. * * @param $extensions * An array of extensions, by reference. * @param $extension_info * Optional. An array of extension info as returned by drush_get_extensions(). */ function _drush_pm_expand_extensions(&$extensions, $extension_info = array()) { if (empty($extension_info)) { $extension_info = drush_get_extensions(); } foreach ($extensions as $key => $extension) { if (($wildcard = rtrim($extension, '*')) !== $extension) { foreach (array_keys($extension_info) as $extension_name) { if (substr($extension_name, 0, strlen($wildcard)) == $wildcard) { $extensions[] = $extension_name; } } unset($extensions[$key]); continue; } } } /** * Command callback. Uninstall one or more modules. * // TODO: Use drupal_execute on system_modules_uninstall_confirm_form so that input is validated. */ function drush_pm_uninstall() { $modules = pm_parse_arguments(func_get_args()); drush_include_engine('drupal', 'environment'); $module_info = drush_get_modules(); $required = drush_drupal_required_modules($module_info); // Discards modules which are enabled, not found or already uninstalled. foreach ($modules as $key => $module) { if (!isset($module_info[$module])) { // The module does not exist in the system. unset($modules[$key]); drush_log(dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)), 'warning'); } else if ($module_info[$module]->status) { // The module is enabled. unset($modules[$key]); drush_log(dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)), 'warning'); } else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED // The module is uninstalled. unset($modules[$key]); drush_log(dt('!module is already uninstalled.', array('!module' => $module)), 'ok'); } else { $dependents = array(); foreach (drush_module_dependents(array($module), $module_info) as $dependent) { if (!in_array($dependent, $required) && ($module_info[$dependent]->schema_version != -1)) { $dependents[] = $dependent; } } if (count($dependents)) { drush_log(dt('To uninstall !module, the following modules must be uninstalled first: !required', array('!module' => $module, '!required' => implode(', ', $dependents))), 'error'); unset($modules[$key]); } } } // Inform the user which modules will finally be uninstalled. if (empty($modules)) { return drush_log(dt('There were no modules that could be uninstalled.'), 'ok'); } else { drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules)))); if(!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } } // Disable the modules. drush_module_uninstall($modules); // Inform the user of final status. foreach ($modules as $module) { drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), 'ok'); } } /** * Command callback. Show available releases for given project(s). */ function drush_pm_releases() { if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) { $requests = array('drupal'); } // Parse out project name and version. $requests = pm_parse_project_version($requests); $info = release_info_get_releases($requests); if (!$info) { return drush_log(dt('No valid projects given.'), 'ok'); } $all = drush_get_option('all', FALSE); $dev = drush_get_option('dev', FALSE); foreach ($info as $name => $project) { $header = dt('------- RELEASES FOR \'!name\' PROJECT -------', array('!name' => strtoupper($name))); $rows = array(); $rows[] = array(dt('Release'), dt('Date'), dt('Status')); $releases = release_info_filter_releases($project['releases'], $all, $dev); foreach ($releases as $release) { $rows[] = array( $release['version'], gmdate('Y-M-d', $release['date']), implode(', ', $release['release_status']) ); } drush_print($header); drush_print_table($rows, TRUE, array(0 => 14)); } return $info; } /** * Command callback. Show release notes for given project(s). */ function drush_pm_releasenotes() { if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) { $requests = array('drupal'); } $requests = pm_parse_project_version($requests); return release_info_print_releasenotes($requests); } /** * Command callback. Refresh update status information. */ function drush_pm_refresh() { // We don't provide for other options here, so we supply an explicit path. drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info'); _pm_refresh(); } /** * Command callback. Execute pm-update. */ function drush_pm_update() { // Call pm-updatecode. updatedb will be called in the post-update process. $args = pm_parse_arguments(func_get_args(), FALSE); drush_set_option('check-updatedb', FALSE); return drush_invoke('pm-updatecode', $args); } /** * Post-command callback. * Execute updatedb command after an updatecode - user requested `update`. */ function drush_pm_post_pm_update() { // Use drush_invoke_process to start a subprocess. Cleaner that way. if (drush_get_context('DRUSH_PM_UPDATED', FALSE) !== FALSE) { drush_invoke_process('@self', 'updatedb'); } } /** * Validate callback for updatecode command. Abort if 'backup' directory exists. */ function drush_pm_updatecode_validate() { $path = drush_get_context('DRUSH_DRUPAL_ROOT') . '/backup'; if (is_dir($path) && (realpath(drush_get_option('backup-dir', FALSE)) != $path)) { return drush_set_error('', dt('Backup directory !path found. It\'s a security risk to store backups inside the Drupal tree. Drush now uses by default ~/drush-backups. You need to move !path out of the Drupal tree to proceed. Note: if you know what you\'re doing you can explicitly set --backup-dir to !path and continue.', array('!path' => $path))); } } /** * Post-command callback for updatecode. * * Execute pm-updatecode-postupdate in a backend process to not conflict with * old code already in memory. */ function drush_pm_post_pm_updatecode() { // Skip if updatecode was invoked by pm-update. // This way we avoid being noisy, as updatedb is to be executed. if (drush_get_option('check-updatedb', TRUE)) { if (drush_get_context('DRUSH_PM_UPDATED', FALSE)) { drush_invoke_process('@self', 'pm-updatecode-postupdate'); } } } /** * Command callback. Execute updatecode-postupdate. */ function drush_pm_updatecode_postupdate() { // Clear the cache, since some projects could have moved around. drush_drupal_cache_clear_all(); // Notify of pending database updates. // Make sure the installation API is available require_once DRUSH_DRUPAL_CORE . '/includes/install.inc'; // Load all .install files. drupal_load_updates(); // @see system_requirements(). foreach (pm_module_list() as $module) { $updates = drupal_get_schema_versions($module); if ($updates !== FALSE) { $default = drupal_get_installed_schema_version($module); if (max($updates) > $default) { drush_log(dt("You have pending database updates. Run `drush updatedb` or visit update.php in your browser."), 'warning'); break; } } } } /** * Sanitize user provided arguments to several pm commands. * * Return an array of arguments off a space and/or comma separated values. */ function pm_parse_arguments($args, $dashes_to_underscores = TRUE) { $arguments = _convert_csv_to_array($args); foreach ($arguments as $key => $argument) { $argument = ($dashes_to_underscores) ? strtr($argument, '-', '_') : $argument; } return $arguments; } /** * Parse out the project name and version and return as a structured array. * * @param $requests an array of project names */ function pm_parse_project_version($requests) { $requestdata = array(); $drupal_version_default = drush_get_context('DRUSH_DRUPAL_MAJOR_VERSION', drush_get_option('default-major', 7)) . '.x'; $drupal_bootstrap = drush_get_context('DRUSH_BOOTSTRAP_PHASE') > 0; foreach($requests as $request) { $drupal_version = $drupal_version_default; $project_version = NULL; $version = NULL; $project = $request; // project-HEAD or project-5.x-1.0-beta // '5.x-' is optional, as is '-beta' preg_match('/-+(HEAD|(?:(\d+\.x)-+)?(\d+\.[\dx]+.*))$/', $request, $matches); if (isset($matches[1])) { // The project is whatever we have prior to the version part of the request. $project = trim(substr($request, 0, strlen($request) - strlen($matches[0])), ' -'); if ($matches[1] == 'HEAD' || $matches[2] == 'HEAD') { drush_log('DRUSH_PM_HEAD', 'Can\'t download HEAD releases because Drupal.org project information only provides for numbered release nodes.', 'warning'); continue; } if (!empty($matches[2])) { // We have a specified Drupal core version. $drupal_version = trim($matches[2], '-.'); } if (!empty($matches[3])) { if (!$drupal_bootstrap && empty($matches[2]) && $project != 'drupal') { // We are not working on a bootstrapped site, and the project is not Drupal itself, // so we assume this value is the Drupal core version and we want the stable project. $drupal_version = trim($matches[3], '-.'); } else { // We are working on a bootstrapped site, or the user specified a Drupal version, // so this value must be a specified project version. $project_version = trim($matches[3], '-.'); if (substr($project_version, -1, 1) == 'x') { // If a dev branch was requested, we add a -dev suffix. $project_version .= '-dev'; } } } } // special checking for 'drupal-VERSION', == drupal latest stable release elseif ((substr($request,0,7) == 'drupal-') && (is_numeric(substr($request,7)))) { $project = 'drupal'; $drupal_version = substr($request,7) . '.x'; $project_version = $version; } if ($project_version) { if ($project == 'drupal') { // For project Drupal, ensure the major version branch is correct, so // we can locate the requested or stable release for that branch. $project_version_array = explode('.', $project_version); $drupal_version = $project_version_array[0] . '.x'; // We use the project version only, since it is core. $version = $project_version; } else { // For regular projects the version string includes the Drupal core version. $version = $drupal_version . '-' . $project_version; } } $requestdata[$project] = array( 'name' => $project, 'version' => $version, 'drupal_version' => $drupal_version, 'project_version' => $project_version, ); } return $requestdata; } /** * @defgroup engines Engine types * @{ */ /** * Implementation of hook_drush_engine_type_info(). */ function pm_drush_engine_type_info() { return array( 'package_handler' => array( 'option' => 'package-handler', 'description' => 'Determine how to fetch projects from update service.', 'default' => 'wget', 'options' => array( 'cache' => 'Cache release XML and tarballs or git clones. Git clones use git\'s --reference option.', ), ), 'release_info' => array( 'add-options-to-command' => TRUE, ), 'update_info' => array( ), 'version_control' => array( 'option' => 'version-control', 'default' => 'backup', 'description' => 'Integrate with version control systems.', ), ); } /** * Used by dl and updatecode commands to determine how to download/checkout new projects and acquire updates to projects. */ function pm_drush_engine_package_handler() { return array( 'wget' => array( 'description' => 'Download project packages using wget or curl.', ), 'git_drupalorg' => array( 'description' => 'Use git.drupal.org to checkout and update projects.', 'options' => array( 'gitusername' => 'Your git username as shown on user/[uid]/edit/git. Typically, this is set this in drushrc.php. Omitting this prevents users from pushing changes back to git.drupal.org.', 'gitsubmodule' => 'Use git submodules for checking out new projects. Existing git checkouts are unaffected, and will continue to (not) use submodules regardless of this setting.', 'gitcheckoutparams' => 'Add options to the `git checkout` command.', 'gitcloneparams' => 'Add options to the `git clone` command.', 'gitfetchparams' => 'Add options to the `git fetch` command.', 'gitpullparams' => 'Add options to the `git pull` command.', 'gitinfofile' => 'Inject version info into each .info file.', ), 'sub-options' => array( 'gitsubmodule' => array( 'gitsubmoduleaddparams' => 'Add options to the `git submodule add` command.', ), ), ), ); } /** * Used by dl and updatecode commands to determine how to download/checkout new projects and acquire updates to projects. */ function pm_drush_engine_release_info() { return array( 'updatexml' => array( 'description' => 'Drush release info engine for update.drupal.org and compatible services.', 'options' => array( 'source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.', 'dev' => 'Work with development releases solely.', ), 'sub-options' => array( 'cache' => array( 'cache-duration-releasexml' => 'Expire duration (in seconds) for release XML. Defaults to 86400 (24 hours).', ), 'select' => array( 'all' => 'Shows all available releases instead of a short list of recent releases.', ), ), ), ); } /** * Integration with VCS in order to easily commit your changes to projects. */ function pm_drush_engine_version_control() { return array( 'backup' => array( 'description' => 'Backup all project files before updates.', 'options' => array( 'no-backup' => 'Do not perform backups.', 'backup-dir' => 'Specify a directory to backup projects into. Defaults to drush-backups within the home directory of the user running the command. It is forbidden to specify a directory inside your drupal root.', ), ), 'bzr' => array( 'signature' => 'bzr root %s', 'description' => 'Quickly add/remove/commit your project changes to Bazaar.', 'options' => array( 'bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.', 'bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also use the --bzrsync option.', ), 'sub-options' => array( 'bzrcommit' => array( 'bzrmessage' => 'Override default commit message which is: Drush automatic commit. Project Command: ', ), ), 'examples' => array( 'drush dl cck --version-control=bzr --bzrsync --bzrcommit' => 'Download the cck project and then add it and commit it to Bazaar.' ), ), 'svn' => array( 'signature' => 'svn info %s', 'description' => 'Quickly add/remove/commit your project changes to Subversion.', 'options' => array( 'svnsync' => 'Automatically add new files to the SVN repository and remove deleted files. Caution.', 'svncommit' => 'Automatically commit changes to SVN repository. You must also using the --svnsync option.', 'svnstatusparams' => "Add options to the 'svn status' command", 'svnaddparams' => 'Add options to the `svn add` command', 'svnremoveparams' => 'Add options to the `svn remove` command', 'svnrevertparams' => 'Add options to the `svn revert` command', 'svncommitparams' => 'Add options to the `svn commit` command', ), 'sub-options' => array( 'svncommit' => array( 'svnmessage' => 'Override default commit message which is: Drush automatic commit: ', ), ), 'examples' => array( 'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).' ), ), ); } /** * @} End of "Engine types". */ /** * Interface for version control systems. * We use a simple object layer because we conceivably need more than one * loaded at a time. */ interface drush_version_control { function pre_update(&$project); function rollback($project); function post_update($project); function post_download($project); static function reserved_files(); } /** * A simple factory function that tests for version control systems, in a user * specified order, and return the one that appears to be appropriate for a * specific directory. */ function drush_pm_include_version_control($directory = '.') { $engine_info = drush_get_engines('version_control'); $version_controls = drush_get_option('version-control', FALSE); // If no version control was given, use a list of defaults. if (!$version_controls) { // Backup engine is the last option. $version_controls = array_reverse(array_keys($engine_info['engines'])); } else { $version_controls = array($version_controls); } // Find the first valid engine in the list, checking signatures if needed. $engine = FALSE; while (!$engine && count($version_controls)) { $version_control = array_shift($version_controls); if (isset($engine_info['engines'][$version_control])) { if (!empty($engine_info['engines'][$version_control]['signature'])) { drush_log(dt('Verifying signature for !vcs version control engine.', array('!vcs' => $version_control)), 'debug'); if (drush_shell_exec($engine_info['engines'][$version_control]['signature'], $directory)) { $engine = $version_control; } } else { $engine = $version_control; } } } if (!$engine) { return drush_set_error('DRUSH_PM_NO_VERSION_CONTROL', dt('No valid version control or backup engine found (the --version-control option was set to "!version-control").', array('!version-control' => $version_control))); } $instance = drush_include_engine('version_control', $version_control); $instance->engine = $engine; return $instance; } /** * Update the locked status of all of the candidate projects * to be updated. * * @param array &$projects * The projects array from pm_updatecode. $project['locked'] will * be set for every file where a persistent lockfile can be found. * The 'lock' and 'unlock' operations are processed first. * @param array $projects_to_lock * A list of projects to create peristent lock files for * @param array $projects_to_unlock * A list of projects to clear the persistent lock on * @param string $lock_message * The reason the project is being locked; stored in the lockfile. * * @return array * A list of projects that are locked. */ function drush_pm_update_lock(&$projects, $projects_to_lock, $projects_to_unlock, $lock_message = NULL) { $locked_result = array(); // Warn about ambiguous lock / unlock values if ($projects_to_lock == array('1')) { $projects_to_lock = array(); drush_log(dt('Ignoring --lock with no value.'), 'warning'); } if ($projects_to_unlock == array('1')) { $projects_to_unlock = array(); drush_log(dt('Ignoring --unlock with no value.'), 'warning'); } // Log if we are going to lock or unlock anything if (!empty($projects_to_unlock)) { drush_log(dt('Unlocking !projects', array('!projects' => implode(',', $projects_to_unlock))), 'ok'); } if (!empty($projects_to_lock)) { drush_log(dt('Locking !projects', array('!projects' => implode(',', $projects_to_lock))), 'ok'); } $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); foreach ($projects as $name => $project) { if ($name == 'drupal') { continue; } $message = NULL; $lockfile = $drupal_root . '/' . $project['path'] . '/.drush-lock-update'; // Remove the lock file if the --unlock option was specified if (((in_array($name, $projects_to_unlock)) || (in_array('all', $projects_to_unlock))) && (file_exists($lockfile))) { drush_op('unlink', $lockfile); } // Create the lock file if the --lock option was specified if ((in_array($name, $projects_to_lock)) || (in_array('all', $projects_to_lock))) { drush_op('file_put_contents', $lockfile, $lock_message != NULL ? $lock_message : "Locked via drush."); // Note that the project is locked. This will work even if we are simulated, // or if we get permission denied from the file_put_contents. // If the lock is -not- simulated or transient, then the lock message will be // read from the lock file below. $message = drush_get_context('DRUSH_SIMULATE') ? 'Simulated lock.' : 'Transient lock.'; } // If the persistent lock file exists, then mark the project as locked. if (file_exists($lockfile)) { $message = trim(file_get_contents($lockfile)); } // If there is a message set, then mark the project as locked. if (isset($message)) { $projects[$name]['locked'] = !empty($message) ? $message : "Locked."; $locked_result[$name] = $project; } } return $locked_result; } function _drush_pm_extension_cache_file() { return drush_get_context('DRUSH_PER_USER_CONFIGURATION') . "/drush-extension-cache.inc"; } function _drush_pm_get_extension_cache() { $extension_cache = array(); $cache_file = _drush_pm_extension_cache_file(); if (file_exists($cache_file)) { include $cache_file; } if (!array_key_exists('extension-map', $extension_cache)) { $extension_cache['extension-map'] = array(); } return $extension_cache; } function drush_pm_lookup_extension_in_cache($extension) { $result = NULL; $extension_cache = _drush_pm_get_extension_cache(); if (!empty($extension_cache) && array_key_exists($extension, $extension_cache)) { $result = $extension_cache[$extension]; } return $result; } function drush_pm_put_extension_cache($extension_cache) { } function drush_pm_cache_project_extensions($project, $found) { $extension_cache = _drush_pm_get_extension_cache(); foreach($found as $extension) { // Simple cache does not handle conflicts // We could keep an array of projects, and count // how many times each one has been seen... $extension_cache[$extension] = $project['name']; } drush_pm_put_extension_cache($extension_cache); } /** * Print out all extensions (modules/themes/profiles) found in specified project. * * Find .info files in the project path and identify modules, themes and * profiles. It handles two kind of projects: drupal core/profiles and * modules/themes. * It does nothing with theme engine projects. */ function drush_pm_extensions_in_project($project) { // Mask for drush_scan_directory, to avoid tests directories. $nomask = array('.', '..', 'CVS', 'tests'); // Drupal core and profiles can contain modules, themes and profiles. if (in_array($project['project_type'], array('core', 'profile'))) { $found = array('profile' => array(), 'theme' => array(), 'module' => array()); // Find all of the .info files foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) { // Find the project type corresponding the .info file. // (Only drupal >=7.x has .info for .profile) $base = dirname($filename) . '/' . $info->name; if (is_file($base . '.module')) { $found['module'][] = $info->name; } else if (is_file($base . '.profile')) { $found['profile'][] = $info->name; } else { $found['theme'][] = $info->name; } } // Special case: find profiles for drupal < 7.x (no .info) if ($project['drupal_version'][0] < 7) { foreach (drush_find_profiles($project['project_install_location']) as $filename => $info) { $found['profile'][] = $info->name; } } // Log results. $msg = "Project !project contains:\n"; $args = array('!project' => $project['name']); foreach (array_keys($found) as $type) { if ($count = count($found[$type])) { $msg .= " - !count_$type !type_$type: !found_$type\n"; $args += array("!count_$type" => $count, "!type_$type" => $type, "!found_$type" => implode(', ', $found[$type])); if ($count > 1) { $args["!type_$type"] = $type.'s'; } } } drush_log(dt($msg, $args), 'success'); drush_print_pipe(call_user_func_array('array_merge', array_values($found))); } // Modules and themes can only contain other extensions of the same type. elseif (in_array($project['project_type'], array('module', 'theme'))) { // Find all of the .info files $found = array(); foreach (drush_scan_directory($project['project_install_location'], "/.*\.info$/", $nomask) as $filename => $info) { $found[] = $info->name; } // Log results. // If there is only one module / theme in the project, only print out // the message if is different than the project name. if (count($found) == 1) { if ($found[0] != $project['name']) { $msg = "Project !project contains a !type named !found."; } } // If there are multiple modules or themes in the project, list them all. else { $msg = "Project !project contains !count !types: !found."; } if (isset($msg)) { drush_print(dt($msg, array('!project' => $project['name'], '!count' => count($found), '!type' => $project['project_type'], '!found' => implode(', ', $found)))); } drush_print_pipe($found); // Cache results. drush_pm_cache_project_extensions($project, $found); } } /** * Return an array of empty directories. * * Walk a directory and return an array of subdirectories that are empty. Will * return the given directory if it's empty. * If a list of items to exclude is provided, subdirectories will be condidered * empty even if they include any of the items in the list. * * @param string $dir * Path to the directory to work in. * @param array $exclude * Array of files or directory to exclude in the check. * * @return array * A list of directory paths that are empty. A directory is deemed to be empty * if it only contains excluded files or directories. */ function drush_find_empty_directories($dir, $exclude = array()) { // Skip files. if (!is_dir($dir)) { return array(); } $to_exclude = array_merge(array('.', '..'), $exclude); $empty_dirs = array(); $dir_is_empty = TRUE; foreach (scandir($dir) as $file) { // Skip excluded directories. if (in_array($file, $to_exclude)) { continue; } // Recurse into sub-directories to find potentially empty ones. $subdir = $dir . '/' . $file; $empty_dirs += drush_find_empty_directories($subdir, $exclude); // $empty_dir will not contain $subdir, if it is a file or if the // sub-directory is not empty. $subdir is only set if it is empty. if (!isset($empty_dirs[$subdir])) { $dir_is_empty = FALSE; } } if ($dir_is_empty) { $empty_dirs[$dir] = $dir; } return $empty_dirs; } /** * Inject metadata into all .info files for a given project. * * @param string $project_dir * The full path to the root directory of the project to operate on. * @param string $project_name * The project machine name (AKA shortname). * @param string $version * The version string to inject into the .info file(s). * * @return boolean * TRUE on success, FALSE on any failures appending data to .info files. */ function drush_pm_inject_info_file_metadata($project_dir, $project_name, $version) { $info_files = drush_scan_directory($project_dir, '/.*\.info$/'); if (!empty($info_files)) { // Construct the string of metadata to append to all the .info files. // Taken straight from: http://drupalcode.org/project/drupalorg.git/blob/refs/heads/6.x-3.x:/drupalorg_project/plugins/release_packager/DrupalorgProjectPackageRelease.class.php#l192 $info = "\n\n; Information added by drush on " . date('Y-m-d') . "\n"; $info .= "version = \"$version\"\n"; // .info files started with 5.x, so we don't have to worry about version // strings like "4.7.x-1.0" in this regular expression. If we can't parse // the version (also from an old "HEAD" release), or the version isn't at // least 6.x, don't add any "core" attribute at all. $matches = array(); if (preg_match('/^((\d+)\.x)-.*/', $version, $matches) && $matches[2] >= 6) { $info .= "core = \"$matches[1]\"\n"; } $info .= "project = \"$project_name\"\n"; $info .= 'datestamp = "'. time() ."\"\n"; $info .= "\n"; foreach ($info_files as $info_file) { if (!drush_file_append_data($info_file->filename, $info)) { return FALSE; } } } return TRUE; } drush-5.10.0/commands/pm/release_info/000077500000000000000000000000001222105546100175705ustar00rootroot00000000000000drush-5.10.0/commands/pm/release_info/updatexml.inc000066400000000000000000000502471222105546100222760ustar00rootroot00000000000000 $release) { $options[$version] = array($version, '-', gmdate('Y-M-d', $release['date']), '-', implode(', ', $release['release_status'])); } $choice = drush_choice($options, dt('Choose one of the available releases for !project:', array('!project' => $request['name']))); if ($choice) { return $project_info['releases'][$choice]; } return FALSE; } /** * Obtain releases info for given requests and fill in status information. * * @param $requests * An array of project names optionally with a version. */ function release_info_get_releases($requests) { $info = array(); foreach ($requests as $name => $request) { $xml = updatexml_get_release_history_xml($request); if (!$xml) { continue; } $project_info = updatexml_get_releases_from_xml($xml, $name); $info[$name] = $project_info; } return $info; } /** * Check if a project is available in a update service. * * Optionally check for consistency by comparing given project type and * the type obtained from the update service. */ function release_info_check_project($request, $type = NULL) { $xml = updatexml_get_release_history_xml($request); if (!$xml) { return FALSE; } if ($type) { $project_type = updatexml_determine_project_type($xml); if ($project_type != $type) { return FALSE; } } return TRUE; } /** * Prints release notes for given projects. * * @param $requests * An array of drupal.org project names optionally with a version. * @param $print_status * Boolean. Used by pm-download to not print a informative note. * @param $tmpfile * If provided, a file that contains contents to show before the * release notes. */ function release_info_print_releasenotes($requests, $print_status = TRUE, $tmpfile = NULL) { $info = release_info_get_releases($requests); if (!$info) { return drush_log(dt('No valid projects given.'), 'ok'); } if (is_null($tmpfile)) { $tmpfile = drush_tempnam('rln-' . implode('-', array_keys($requests)) . '.'); } foreach ($info as $name => $project) { $selected_versions = array(); // If the request includes version, show the release notes for this version. if (isset($requests[$name]['version'])) { $selected_versions[] = $requests[$name]['version']; } else { // If requested project is installed, // show release notes for the installed version and all newer versions. if (isset($project['recommended'], $project['installed'])) { $releases = array_reverse($project['releases']); foreach($releases as $version => $release) { if ($release['date'] >= $project['releases'][$project['installed']]['date']) { $release += array('version_extra' => ''); $project['releases'][$project['installed']] += array('version_extra' => ''); if ($release['version_extra'] == 'dev' && $project['releases'][$project['installed']]['version_extra'] != 'dev') { continue; } $selected_versions[] = $version; } } } else { // Project is not installed and user did not specify a version, // so show the release notes for the recommended version. $selected_versions[] = $project['recommended']; } } foreach ($selected_versions as $version) { if (!isset($project['releases'][$version]['release_link'])) { drush_log(dt("Project !project does not have release notes for version !version.", array('!project' => $name, '!version' => $version)), 'warning'); continue; } $release_link = $project['releases'][$version]['release_link']; $filename = drush_download_file($release_link, drush_tempnam($name)); @$dom = DOMDocument::loadHTMLFile($filename); if ($dom) { drush_log(dt("Successfully parsed and loaded the HTML contained in the release notes' page for !project (!version) project.", array('!project' => $name, '!version' => $version)), 'notice'); } else { drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $name)), 'error'); continue; } $xml = simplexml_import_dom($dom); $node_content = $xml->xpath('//*/div[@class="node-content"]'); // Prepare the project notes to print. $notes_last_update = $node_content[0]->div[1]->div[0]->asXML(); unset($node_content[0]->div); $project_notes = $node_content[0]->asXML(); // Build the status message. $status_msg = '> ' . implode(', ', $project['releases'][$version]['release_status']); $break = '
'; $notes_header = dt("
> RELEASE NOTES FOR '!name' PROJECT, VERSION !version:!break > !time.!break !status
", array('!status' => $print_status ? $status_msg : '', '!name' => strtoupper($name), '!break' => $break, '!version' => $version, '!time' => $notes_last_update)); // Finally print the release notes for the requested project. if (drush_get_option('html', FALSE)) { $print = $notes_header . $project_notes; } else { $print = drush_html_to_text($notes_header . $project_notes . "\n", array('br', 'p', 'ul', 'ol', 'li', 'hr')); if (drush_drupal_major_version() < 7) { $print .= "\n"; } } file_put_contents($tmpfile, $print, FILE_APPEND); } } drush_print_file($tmpfile); } /** * Helper function for release_info_filter_releases(). */ function _release_info_compare_date($a, $b) { if ($a['date'] == $b['date']) { return 0; } if ($a['version_major'] == $b['version_major']) { return ($a['date'] > $b['date']) ? -1 : 1; } return ($a['version_major'] > $b['version_major']) ? -1 : 1; } /** * Filter a list of releases. * * @param $releases * Array of release information * @param $all * Show all releases. If FALSE, shows only the first release that is * Recommended or Supported or Security or Installed. * @param String $restrict_to * If set to 'dev', show only development release. * @param $show_all_until_installed * If TRUE, then all releases will be shown until the INSTALLED release * is found, at which point the algorithm will stop. */ function release_info_filter_releases($releases, $all = FALSE, $restrict_to = '', $show_all_until_installed = TRUE) { // Start off by sorting releases by release date. uasort($releases, '_release_info_compare_date'); // Find version_major for the installed release. $installed_version_major = FALSE; foreach ($releases as $version => $release_info) { if (in_array("Installed", $release_info['release_status'])) { $installed_version_major = $release_info['version_major']; } } // Now iterate through and filter out the releases we're interested in. $options = array(); $limits_list = array(); $dev = $restrict_to == 'dev'; foreach ($releases as $version => $release_info) { if (!$dev || ((array_key_exists('version_extra', $release_info)) && ($release_info['version_extra'] == 'dev'))) { $saw_unique_status = FALSE; foreach ($release_info['release_status'] as $one_status) { // We will show the first release of a given kind; // after we show the first security release, we show // no other. We do this on a per-major-version basis, // though, so if a project has three major versions, then // we will show the first security release from each. // This rule is overridden by $all and $show_all_until_installed. $test_key = $release_info['version_major'] . $one_status; if (!array_key_exists($test_key, $limits_list)) { $limits_list[$test_key] = TRUE; $saw_unique_status = TRUE; // Once we see the "Installed" release we will stop // showing all releases if ($one_status == "Installed") { $show_all_until_installed = FALSE; $installed_release_date = $release_info['date']; } } } if ($all || ($show_all_until_installed && ($installed_version_major == $release_info['version_major'])) || $saw_unique_status) { $options[$release_info['version']] = $release_info; } } } // If "show all until installed" is still true, that means that // we never encountered the installed release anywhere in releases, // and therefore we did not filter out any releases at all. If this // is the case, then call ourselves again, this time with // $show_all_until_installed set to FALSE from the beginning. // The other situation we might encounter is when we do not encounter // the installed release, and $options is still empty. This means // that there were no supported or recommented or security or development // releases found. If this is the case, then we will force ALL to TRUE // and show everything on the second iteration. if (($all === FALSE) && ($show_all_until_installed === TRUE)) { $options = release_info_filter_releases($releases, empty($options), $restrict_to, FALSE); } return $options; } /** * Pick most appropriate release from XML list or ask the user if no one fits. * * @param array $request * An array with project and version strings as returned by * pm_parse_project_version(). * @param resource $xml * A handle to the XML document. * @param String $restrict_to * One of: * 'dev': Forces a -dev release. * 'version': Forces a point release. * '': No restriction (auto-selects latest recommended or supported release if requested release is not found). * Default is ''. */ function updatexml_parse_release($request, $xml, $restrict_to = '') { if (!empty($request['version'])) { $matches = array(); // See if we only have a branch version. if (preg_match('/^\d+\.x-(\d+)$/', $request['version'], $matches)) { $xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$matches[1] . "]"; $releases = @$xml->xpath($xpath_releases); } else { // In some cases, the request only says something like '7.x-3.x' but the // version strings include '-dev' on the end, so we need to append that // here for the xpath to match below. if (substr($request['version'], -2) == '.x') { $request['version'] .= '-dev'; } $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']"); if (empty($releases)) { if (empty($restrict_to)) { drush_log(dt("Could not locate !project version !version, will try to download latest recommended or supported release.", array('!project' => $request['name'], '!version' => $request['version'])), 'warning'); } else { drush_log(dt("Could not locate !project version !version.", array('!project' => $request['name'], '!version' => $request['version'])), 'warning'); return FALSE; } } } } if ($restrict_to == 'dev') { $releases = @$xml->xpath("/project/releases/release[status='published'][version_extra='dev']"); if (empty($releases)) { drush_print(dt('There is no development release for project !project.', array('!type' => $release_type, '!project' => $request['name']))); return FALSE; } } // If that did not work, we will get the first published release for the // recommended major version or fallback to other supported major versions. if (empty($releases)) { foreach(array('recommended_major', 'supported_majors') as $release_type) { if ($versions = $xml->xpath("/project/$release_type")) { $xpath = "/project/releases/release[status='published'][version_major=" . (string)$versions[0] . "]"; $releases = @$xml->xpath($xpath); if (!empty($releases)) { break; } } } } // If there are releases found, let's try first to fetch one with no // 'version_extra'. Otherwise, use all. if (!empty($releases)) { $stable_releases = array(); foreach ($releases as $one_release) { if (!array_key_exists('version_extra', $one_release)) { $stable_releases[] = $one_release; } } if (!empty($stable_releases)) { $releases = $stable_releases; } } if (empty($releases)) { drush_print(dt('There are no releases for project !project.', array('!project' => $request['name']))); return FALSE; } // First published release is just the first value in $releases. return (array)$releases[0]; } /** * Download the release history xml for the specified request. */ function updatexml_get_release_history_xml($request) { $status_url = isset($request['status url'])?$request['status url']:RELEASE_INFO_DEFAULT_URL; $url = $status_url . '/' . $request['name'] . '/' . $request['drupal_version']; drush_log('Downloading release history from ' . $url); // Some hosts have allow_url_fopen disabled. if ($path = drush_download_file($url, drush_tempnam($request['name']), drush_get_option('cache-duration-releasexml', 24*3600))) { $xml = simplexml_load_file($path); } if (!$xml) { // We are not getting here since drupal.org always serves an XML response. return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url))); } if ($error = $xml->xpath('/error')) { // Don't set an error here since it stops processing during site-upgrade. drush_log($error[0], 'warning'); // 'DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE', return FALSE; } // Unpublished project? $project_status = $xml->xpath('/project/project_status'); if ($project_status[0][0] == 'unpublished') { return drush_set_error('DRUSH_PM_PROJECT_UNPUBLISHED', dt("Project !project is unpublished and has no releases available.", array('!project' => $request['name'])), 'warning'); } return $xml; } /** * Obtain releases for a project's xml as returned by the update service. */ function updatexml_get_releases_from_xml($xml, $project) { // If bootstraped, we can obtain which is the installed release of a project. static $installed_projects = FALSE; if (!$installed_projects) { if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) { $installed_projects = drush_get_projects(); } else { $installed_projects = array(); } } foreach (array('title', 'short_name', 'dc:creator', 'api_version', 'recommended_major', 'supported_majors', 'default_major', 'project_status', 'link') as $item) { if (array_key_exists($item, $xml)) { $value = $xml->xpath($item); $project_info[$item] = (string)$value[0]; } } $recommended_major = @$xml->xpath("/project/recommended_major"); $recommended_major = empty($recommended_major)?"":(string)$recommended_major[0]; $supported_majors = @$xml->xpath("/project/supported_majors"); $supported_majors = empty($supported_majors)?array():array_flip(explode(',', (string)$supported_majors[0])); $releases_xml = @$xml->xpath("/project/releases/release[status='published']"); $recommended_version = NULL; $latest_version = NULL; foreach ($releases_xml as $release) { $release_info = array(); foreach (array('name', 'version', 'tag', 'version_major', 'version_extra', 'status', 'release_link', 'download_link', 'date', 'mdhash', 'filesize') as $item) { if (array_key_exists($item, $release)) { $value = $release->xpath($item); $release_info[$item] = (string)$value[0]; } } $statuses = array(); if (array_key_exists($release_info['version_major'], $supported_majors)) { $statuses[] = "Supported"; unset($supported_majors[$release_info['version_major']]); } if ($release_info['version_major'] == $recommended_major) { if (!isset($latest_version)) { $latest_version = $release_info['version']; } // The first stable version (no 'version extra') in the recommended major // is the recommended release if (empty($release_info['version_extra']) && (!isset($recommended_version))) { $statuses[] = "Recommended"; $recommended_version = $release_info['version']; } } if (!empty($release_info['version_extra']) && ($release_info['version_extra'] == "dev")) { $statuses[] = "Development"; } foreach ($release->xpath('terms/term/value') as $release_type) { // There are three kinds of release types that we recognize: // "Bug fixes", "New features" and "Security update". // We will add "Security" for security updates, and nothing // for the other kinds. if (strpos($release_type, "Security") !== FALSE) { $statuses[] = "Security"; } } // Add to status whether the project is installed. if (isset($installed_projects[$project])) { if ($installed_projects[$project]['version'] == $release_info['version']) { $statuses[] = dt('Installed'); $project_info['installed'] = $release_info['version']; } } $release_info['release_status'] = $statuses; $releases[$release_info['version']] = $release_info; } // If there is no -stable- release in the recommended major, // then take the latest verion in the recommended major to be // the recommended release. if (!isset($recommended_version) && isset($latest_version)) { $recommended_version = $latest_version; $releases[$recommended_version]['release_status'][] = "Recommended"; } $project_info['releases'] = $releases; $project_info['recommended'] = $recommended_version; return $project_info; } /** * Determine a project type from its update service xml. */ function updatexml_determine_project_type($xml) { $project_types = array( 'core' => 'Drupal core', 'profile' => 'Distributions', 'profile-legacy' => 'Installation profiles', 'module' => 'Modules', 'theme' => 'Themes', 'theme engine' => 'Theme engines', 'translation' => 'Translations' ); $project_types_xpath = '(value="' . implode('" or value="', $project_types) . '")'; $type = 'module'; if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) { $type = array_search($types[0]->value, $project_types); $type = ($type == 'profile-legacy') ? 'profile' : $type; } return $type; } drush-5.10.0/commands/pm/update_info/000077500000000000000000000000001222105546100174325ustar00rootroot00000000000000drush-5.10.0/commands/pm/update_info/drupal.inc000066400000000000000000000115501222105546100214160ustar00rootroot00000000000000 $project) { if (empty($available[$key])) { update_create_fetch_task($project); continue; } if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) { $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING; } if (empty($available[$key]['releases'])) { $available[$key]['fetch_status'] = UPDATE_FETCH_PENDING; } if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) { update_create_fetch_task($project); } } // Set a batch to process all pending tasks. $batch = array( 'operations' => array( array('update_fetch_data_batch', array()), ), 'finished' => 'update_fetch_data_finished', 'file' => drupal_get_path('module', 'update') . '/update.fetch.inc', ); batch_set($batch); drush_backend_batch_process(); // Clear any error set by a failed update fetch task. This avoid rollbacks. drush_clear_error(); // Calculate update status data. $available = _update_get_cached_available_releases(); $data = update_calculate_project_data($available); foreach ($data as $project_name => $project) { // Discard custom projects. if ($project['status'] == UPDATE_UNKNOWN) { unset($data[$project_name]); continue; } // Discard projects with unknown installation path. if ($project_name != 'drupal' && !isset($projects[$project_name]['path'])) { unset($data[$project_name]); continue; } // Allow to update disabled projects. if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) { $data[$project_name]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-')); } // Add some info from the project to $data. $data[$project_name] += array( 'path' => $projects[$project_name]['path'], 'label' => $projects[$project_name]['label'], ); // Store all releases, not just the ones selected by update.module. $data[$project_name]['releases'] = $available[$project_name]['releases']; } return $data; } function pm_get_project_info($projects) { $data = array(); include_once drupal_get_path('module', 'update') .'/update.fetch.inc'; foreach ($projects as $project_name => $project) { $url = UPDATE_DEFAULT_URL. "/$project_name/". drush_drupal_major_version() . '.x'; $xml = drupal_http_request($url); if (isset($xml->error)) { drush_set_error(dt( 'HTTP Request to @request has failed. @error', array('@request' => $xml->request, '@error' => $xml->error) )); } elseif (!$info = update_parse_xml($xml->data)) { drush_set_error(dt( 'No release history found for @project_name', array('@project_name' => $project_name) )); } else { $data[$project_name] = $info; } } return $data; } drush-5.10.0/commands/pm/update_info/drupal_6.inc000066400000000000000000000064021222105546100216430ustar00rootroot00000000000000 $project) { // Discard custom projects. if ($project['status'] == UPDATE_UNKNOWN) { unset($data[$project_name]); continue; } // Discard projects with unknown installation path. if ($project_name != 'drupal' && !isset($projects[$project_name]['path'])) { unset($data[$project_name]); continue; } // Allow to update disabled projects. if (in_array($project['project_type'], array('disabled-module', 'disabled-theme'))) { $data[$project_name]['project_type'] = substr($project['project_type'], strpos($project['project_type'], '-') + 1); } // Add some info from the project to $data. $data[$project_name] += array( 'path' => $projects[$project_name]['path'], 'label' => $projects[$project_name]['label'], ); } return $data; } /** * Get project information from drupal.org. * * @param $projects An array of project names */ function pm_get_project_info($projects) { $info = array(); $data = array(); foreach ($projects as $project_name => $project) { $url = UPDATE_DEFAULT_URL. "/$project_name/". drush_drupal_major_version() . '.x'; $xml = drupal_http_request($url); $data[] = $xml->data; } if ($data) { include_once drupal_get_path('module', 'update') .'/update.fetch.inc'; $parser = new update_xml_parser; $info = $parser->parse($data); } return $info; } drush-5.10.0/commands/pm/updatecode.pm.inc000066400000000000000000000622061222105546100203660ustar00rootroot00000000000000 $extension) { // Add an item to $update_info for each enabled extension which was obtained // from cvs or git and its project is unknown (because of cvs_deploy or // git_deploy is not enabled). if (!isset($extension->info['project'])) { if ((isset($extension->vcs)) && ($extension->status)) { $update_info[$name] = array( 'label' => $extension->label, 'existing_version' => 'Unknown', 'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED, 'status_msg' => dt('Project was not packaged by drupal.org but obtained from !vcs. You need to enable !vcs_deploy module', array('!vcs' => $extension->vcs)), ); // The user may have requested to update a project matching this // extension. If it was by coincidence or error we don't mind as we've // already added an item to $update_info. Just clean up $requests. if (isset($requests[$name])) { unset($requests[$name]); } } } // Aditionally if the extension name is distinct to the project name and // the user asked to update the extension, fix the request. elseif ((isset($requests[$name])) && ($extension->name != $extension->info['project'])) { $requests[$extension->info['project']] = $requests[$name]; unset($requests[$name]); } } // Add an item to $update_info for each request not present in $update_info. foreach ($requests as $name => $request) { if (!isset($update_info[$name])) { // Disabled projects. if ((isset($projects[$name])) && ($projects[$name]['status'] == 0)) { $update_info[$name] = array( 'label' => $projects[$name]['label'], 'existing_version' => $projects[$name]['version'], 'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE, ); unset($requests[$name]); } // At this point we are unable to find matching installed project. // It does not exist at all or it is mispelled,... else { $update_info[$name] = array( 'label' => $name, 'existing_version' => 'Unknown', 'status'=> DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND, ); } } } // If specific versions were requested, match the requested release. foreach ($requests as $name => $request) { if (!empty($request['version'])) { $release = pm_get_release($request, $update_info[$name]); if (!$release) { $update_info[$name]['status'] = DRUSH_PM_REQUESTED_VERSION_NOT_FOUND; } else if ($release['version'] == $update_info[$name]['existing_version']) { $update_info[$name]['status'] = DRUSH_PM_REQUESTED_CURRENT; } else { $update_info[$name]['status'] = DRUSH_PM_REQUESTED_UPDATE; } // Set the candidate version to the requested release. $update_info[$name]['candidate_version'] = $release['version']; } } // Table headers. $rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status')); // Process releases, notifying user of status and // building a list of proposed updates. $updateable = pm_project_filter($update_info, $rows, $security_only); // Pipe preparation. if (drush_get_context('DRUSH_PIPE')) { $pipe = ""; foreach($updateable as $project) { $pipe .= $project['name']. " "; $pipe .= $project['existing_version']. " "; $pipe .= $project['candidate_version']. " "; $pipe .= str_replace(' ', '-', pm_update_filter($project)). "\n"; } drush_print_pipe($pipe); // Automatically curtail update process if in pipe mode. $updateable = array(); } $tmpfile = drush_tempnam('pm-updatecode.'); $last = pm_update_last_check(); drush_print(dt('Update information last refreshed: ') . ($last ? format_date($last) : dt('Never'))); drush_print(); drush_print(dt("Update status information on all installed and enabled Drupal projects:")); // Cache the output of drush_print_table in $tmpfile so that we can // include it in the paginated output later. $tmphandle = fopen($tmpfile, 'a'); drush_print_table($rows, TRUE, array(3 => 50), $tmphandle); fclose($tmphandle); $contents = file_get_contents($tmpfile); drush_print($contents); drush_print(); // If specific project updates were requested then remove releases for all // others. $requested = func_get_args(); if (!empty($requested)) { foreach ($updateable as $name => $project) { if (!isset($requests[$name])) { unset($updateable[$name]); } } } // Prevent update of core if --no-core was specified. if (isset($updateable['drupal']) && drush_get_option('no-core', FALSE)) { unset($updateable['drupal']); drush_print(dt('Skipping core update (--no-core specified).')); } // If there are any locked projects that were not requested, then remove them. if (!empty($locked_list)) { foreach ($updateable as $name => $project) { if ((isset($locked_list[$name])) && (!isset($requests[$name]))) { unset($updateable[$name]); } } } // Do no updates in simulated mode. if (drush_get_context('DRUSH_SIMULATE')) { return drush_log(dt('No action taken in simulated mode.'), 'ok'); return TRUE; } $core_update_available = FALSE; if (isset($updateable['drupal'])) { $drupal_project = $updateable['drupal']; unset($update_info['drupal']); unset($updateable['drupal']); // At present we need to update drupal core after non-core projects // are updated. if (empty($updateable)) { return _pm_update_core($drupal_project, $tmpfile); } // If there are modules other than drupal core enabled, then update them // first. else { $core_update_available = TRUE; if ($drupal_project['status'] == UPDATE_NOT_SECURE) { drush_print(dt("NOTE: A security update for the Drupal core is available.")); } else { drush_print(dt("NOTE: A code update for the Drupal core is available.")); } drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n")); } } // If there are no releases to update, then print a final // exit message. if (empty($updateable)) { if ($security_only) { return drush_log(dt('No security updates available.'), 'ok'); } else { return drush_log(dt('No code updates available.'), 'ok'); } } // Offer to update to the identified releases. if (!pm_update_packages($updateable, $tmpfile)) { return FALSE; } // After projects are updated we can update core. if ($core_update_available) { drush_print(); return _pm_update_core($drupal_project, $tmpfile); } } /** * Update drupal core, following interactive confirmation from the user. * * @param $project * The drupal project information from the drupal.org update service, * copied from $update_info['drupal']. @see drush_pm_updatecode. */ function _pm_update_core(&$project, $tmpfile) { $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); drush_print(dt('Code updates will be made to drupal core.')); drush_print(dt("WARNING: Updating core will discard any modifications made to Drupal core files, most noteworthy among these are .htaccess and robots.txt. If you have made any modifications to these files, please back them up before updating so that you can re-create your modifications in the updated version of the file.")); drush_print(dt("Note: Updating core can potentially break your site. It is NOT recommended to update production sites without prior testing.")); drush_print(); if (drush_get_option('notes', FALSE)) { drush_print('Obtaining release notes for above projects...'); $requests = pm_parse_project_version(array('drupal')); release_info_print_releasenotes($requests, TRUE, $tmpfile); } if(!drush_confirm(dt('Do you really want to continue?'))) { drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.')); return drush_user_abort(); } // We need write permission on $drupal_root. if (!is_writable($drupal_root)) { return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.')); } // Create a directory 'core' if it does not already exist. $project['path'] = 'drupal-' . $project['candidate_version']; $project['full_project_path'] = $drupal_root . '/' . $project['path']; if (!is_dir($project['full_project_path'])) { drush_mkdir($project['full_project_path']); } // Create a list of directories to exclude from the update process. $skip_list = array('sites', $project['path']); // Add non-writable directories: we can't move them around. // We will also use $items_to_test later for $version_control check. $items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE); foreach (array_keys($items_to_test) as $item) { if (is_dir($item) && !is_writable($item)) { $skip_list[] = $item; unset($items_to_test[$item]); } elseif (is_link($item)) { $skip_list[] = $item; unset($items_to_test[$item]); } } $project['skip_list'] = $skip_list; // Move all files and folders in $drupal_root to the new 'core' directory // except for the items in the skip list _pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']); // Set a context variable to indicate that rollback should reverse // the _pm_update_move_files above. drush_set_context('DRUSH_PM_DRUPAL_CORE', $project); if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) { return FALSE; } $project['base_project_path'] = dirname($project['full_project_path']); // Check we have a version control system, and it clears its pre-flight. if (!$version_control->pre_update($project, $items_to_test)) { return FALSE; } // Package handlers want the project directory in project_dir. $project['project_dir'] = $project['path']; // Update core. if (pm_update_project($project, $version_control) === FALSE) { return FALSE; } // Take the updated files in the 'core' directory that have been updated, // and move all except for the items in the skip list back to // the drupal root _pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']); drush_delete_dir($project['full_project_path']); // Version control engines expect full_project_path to exist and be accurate. $project['full_project_path'] = $project['base_project_path']; // If there is a backup target, then find items // in the backup target that do not exist at the // drupal root. These are to be moved back. if (array_key_exists('backup_target', $project)) { _pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE); _pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE); } pm_update_complete($project, $version_control); return TRUE; } /** * Move some files from one location to another */ function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) { $items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE); foreach ($items_to_move as $filename => $info) { if ($remove_conflicts) { drush_delete_dir($dest_dir . '/' . basename($filename)); } if (!file_exists($dest_dir . '/' . basename($filename))) { $move_result = drush_move_dir($filename, $dest_dir . '/' . basename($filename)); } } return TRUE; } /** * Update projects according to an array of releases and print the release notes * for each project, following interactive confirmation from the user. * * @param $update_info * An array of projects from the drupal.org update service, with an additional * array key candidate_version that specifies the version to be installed. */ function pm_update_packages($update_info, $tmpfile) { $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); $print = ''; $status = array(); foreach($update_info as $project) { $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], "; $status[$project['status']] = $project['status']; } // We print the list of the projects that need to be updated. if (isset($status[UPDATE_NOT_SECURE])) { if (isset($status[UPDATE_NOT_CURRENT])) { $title = (dt('Security and code updates will be made to the following projects:')); } else { $title = (dt('Security updates will be made to the following projects:')); } } else { $title = (dt('Code updates will be made to the following projects:')); } $print = "$title " . (substr($print, 0, strlen($print)-2)); drush_print($print); file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND); // Print the release notes for projects to be updated. if (drush_get_option('notes', FALSE)) { drush_print('Obtaining release notes for above projects...'); $requests = pm_parse_project_version(array_keys($update_info)); release_info_print_releasenotes($requests, TRUE, $tmpfile); } // We print some warnings before the user confirms the update. drush_print(); if (drush_get_option('no-backup', FALSE)) { drush_print(dt("Note: You have selected to not store backups.")); } else { drush_print(dt("Note: A backup of your project will be stored to backups directory if it is not managed by a supported version control system.")); drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.')); } if(!drush_confirm(dt('Do you really want to continue with the update process?'))) { return drush_user_abort(); } // Now we start the actual updating. foreach($update_info as $project) { if (empty($project['path'])) { return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type']))); } drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path']))); // Define and check the full path to project directory and base (parent) directory. $project['full_project_path'] = $drupal_root . '/' . $project['path']; if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) { return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path']))); } if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) { return FALSE; } $project['base_project_path'] = dirname($project['full_project_path']); // Check we have a version control system, and it clears its pre-flight. if (!$version_control->pre_update($project)) { return FALSE; } // Package handlers want the name of the directory in project_dir. // It may be different to the project name for pm-download. // Perhaps we want here filename($project['full_project_path']). $project['project_dir'] = $project['name']; // Run update on one project. if (pm_update_project($project, $version_control) === FALSE) { return FALSE; } pm_update_complete($project, $version_control); } return TRUE; } /** * Update one project -- a module, theme or Drupal core. * * @param $project * The project to upgrade. $project['full_project_path'] must be set * to the location where this project is stored. */ function pm_update_project($project, $version_control) { // 1. If the version control engine is a proper vcs we need to remove project // files in order to not have orphan files after update. // 2. If the package-handler is cvs or git, it will remove upstream removed // files and no orphans will exist after update. // So, we must remove all files previous update if the directory is not a // working copy of cvs or git but we don't need to remove them if the version // control engine is backup, as it did already move the project out to the // backup directory. if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) { // Find and unlink all files but the ones in the vcs control directories. $skip_list = array('.', '..'); $skip_list = array_merge($skip_list, drush_version_control_reserved_files()); drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE); } // Add the project to a context so we can roll back if needed. $updated = drush_get_context('DRUSH_PM_UPDATED'); $updated[] = $project; drush_set_context('DRUSH_PM_UPDATED', $updated); if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) { return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name']))); } // If the version control engine is a proper vcs we also need to remove // orphan directories. if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) { $files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files()); array_map('drush_delete_dir', $files); } return TRUE; } /** * Run the post-update hooks after updatecode is complete for one project. */ function pm_update_complete($project, $version_control) { drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version']))); drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']]); $version_control->post_update($project); } function drush_pm_updatecode_rollback() { $projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array())); foreach($projects as $project) { drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title']))); // Check we have a version control system, and it clears it's pre-flight. if (!$version_control = drush_pm_include_version_control($project['path'])) { return FALSE; } $version_control->rollback($project); } // Post rollback, we will do additional repair if the project is drupal core. $drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE); if ($drupal_core) { $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); _pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']); drush_delete_dir($drupal_core['full_project_path']); } } /** * Return an array of updateable projects and fill $rows. * * Array of updateable projects is obtained from calculated project update * status and $security_only flag. */ function pm_project_filter(&$update_info, &$rows, $security_only) { $updateable = array(); foreach ($update_info as $key => $project) { switch($project['status']) { case DRUSH_PM_REQUESTED_UPDATE: $status = dt('Specified version available'); $project['updateable'] = TRUE; break; case DRUSH_PM_REQUESTED_CURRENT: $status = dt('Specified version already installed'); break; case DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED: $status = $project['status_msg']; break; case DRUSH_PM_REQUESTED_VERSION_NOT_FOUND: $status = dt('Specified version not found'); break; case DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND: $status = dt('Specified project not found'); break; case DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE: $status = dt('Project has no enabled extensions and can\'t be updated'); break; default: $status = pm_update_filter($project); break; } if (isset($project['locked'])) { $status = $project['locked'] . " ($status)"; } // Persist candidate_version in $update_info (plural). if (empty($project['candidate_version'])) { $update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change } else { $update_info[$key]['candidate_version'] = $project['candidate_version']; } if (!empty($project['updateable'])) { $updateable[$key] = $project; // Find only security updates if ($security_only && ($project['status'] != UPDATE_NOT_SECURE)) { unset($updateable[$key]); } } $rows[] = array($project['label'], $project['existing_version'], $update_info[$key]['candidate_version'], $status); } return $updateable; } /** * Set a release to a recommended version (if available), and set as updateable. */ function pm_release_recommended(&$project) { if (isset($project['recommended'])) { $project['candidate_version'] = $project['recommended']; $project['updateable'] = TRUE; } // If installed version is dev and the candidate version is older, choose // latest dev as candidate. if (($project['install_type'] == 'dev') && isset($project['candidate_version'])) { if ($project['releases'][$project['candidate_version']]['date'] < $project['info']['datestamp']) { $project['candidate_version'] = $project['latest_dev']; if ($project['releases'][$project['candidate_version']]['date'] <= $project['info']['datestamp']) { $project['candidate_version'] = $project['existing_version']; $project['updateable'] = FALSE; } } } } /** * Get the a best release match for a requested update. * * @param $request A information array for the requested project * @param $project A project information array for this project, as returned by an update service from pm_get_extensions() */ function pm_get_release($request, $project) { $minor = ''; $version_patch_changed = ''; if ($request['version']) { // The user specified a specific version - try to find that exact version foreach($project['releases'] as $version => $release) { // Ignore unpublished releases. if ($release['status'] != 'published') { continue; } // Straight match if (!isset($recommended_version) && $release['version'] == $request['version']) { $recommended_version = $version; } } } else { // No version specified - try to find the best version we can foreach($project['releases'] as $version => $release) { // Ignore unpublished releases. if ($release['status'] != 'published') { continue; } // If we haven't found a recommended version yet, put the dev // version as recommended and hope it gets overwritten later. // Look for the 'latest version' if we haven't found it yet. // Latest version is defined as the most recent version for the // default major version. if (!isset($latest_version) && $release['version_major'] == $project['default_major']) { $latest_version = $version; } if (!isset($recommended_version) && $release['version_major'] == $project['default_major']) { if ($minor != $release['version_patch']) { $minor = $release['version_patch']; $version_patch_changed = $version; } if (empty($release['version_extra']) && $minor == $release['version_patch']) { $recommended_version = $version_patch_changed; } continue; } } } if (isset($recommended_version)) { return $project['releases'][$recommended_version]; } else if (isset($latest_version)) { return $project['releases'][$latest_version]; } else { return false; } } drush-5.10.0/commands/pm/version_control/000077500000000000000000000000001222105546100203625ustar00rootroot00000000000000drush-5.10.0/commands/pm/version_control/backup.inc000066400000000000000000000046101222105546100223230ustar00rootroot00000000000000prepare_backup_dir()) { if ($project['project_type'] != 'core') { $backup_target .= '/' . $project['project_type'] . 's'; drush_mkdir($backup_target); } $backup_target .= '/'. $project['name']; // Save for rollback or notifications. $project['backup_target'] = $backup_target; // Move or copy to backup target based in package-handler. if (drush_get_option('package-handler', 'wget') == 'wget') { if (drush_move_dir($project['full_project_path'], $backup_target)) { return TRUE; } } // cvs or git. elseif (drush_copy_dir($project['full_project_path'], $backup_target)) { return TRUE; } return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $project['full_project_path'], '!backup_target' => $backup_target))); } } /** * Implementation of rollback(). */ public function rollback($project) { if (drush_get_option('no-backup', FALSE)) { return; } if (drush_move_dir($project['backup_target'], $project['full_project_path'], TRUE)) { return drush_log(dt("Backups were restored successfully."), 'ok'); } return drush_set_error('DRUSH_PM_BACKUP_ROLLBACK_FAILED', dt('Could not restore backup and rollback from failed upgrade. You will need to resolve manually.')); } /** * Implementation of post_update(). */ public function post_update($project) { if (drush_get_option('no-backup', FALSE)) { return; } if ($project['backup_target']) { drush_log(dt("Backups were saved into the directory !backup_target.", array('!backup_target' => $project['backup_target'])), 'ok'); } } /** * Implementation of post_download(). */ public function post_download($project) { // NOOP } // Helper for pre_update. public function prepare_backup_dir($subdir = NULL) { return drush_prepare_backup_dir($subdir); } public static function reserved_files() { return array(); } } drush-5.10.0/commands/pm/version_control/bzr.inc000066400000000000000000000134751222105546100216640ustar00rootroot00000000000000 '.'); } $args = array_keys($items_to_test); array_unshift($args, 'bzr status --short' . str_repeat(' %s', count($args))); array_unshift($args, $project['full_project_path']); if (call_user_func_array('drush_shell_cd_and_exec', $args)) { $output = preg_grep('/^[\sRCP][\sNDKM][\s\*]/', drush_shell_exec_output()); if (!empty($output)) { return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); } } else { return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } return TRUE; } /** * Implementation of rollback(). */ public function rollback($project) { if (drush_shell_exec('bzr revert %s', $project['full_project_path'])) { $output = drush_shell_exec_output(); if (!empty($output)) { return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); } } else { return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the Bazaar status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } } /** * Implementation of post_update(). */ public function post_update($project) { if ($this->sync($project)) { // Only attempt commit on a sucessful sync $this->commit($project); } } /** * Implementation of post_download(). */ public function post_download($project) { if ($this->sync($project)) { // Only attempt commit on a sucessful sync $this->commit($project); } } /** * Automatically add any unversioned files to Bazaar and remove any files * that have been deleted on the file system */ private function sync($project) { if (drush_get_option('bzrsync')) { $errors = ''; $root = array(); if (drush_shell_exec('bzr status --short %s', $project['full_project_path'])) { $output = drush_shell_exec_output(); // All paths returned by bzr status are relative to the repository root. if (drush_shell_exec('bzr root %s', $project['full_project_path'])) { $root = drush_shell_exec_output(); } foreach ($output as $line) { if (preg_match('/^\?\s+(.*)/', $line, $matches)) { $path = $root[0] .'/'. $matches[1]; if (!drush_shell_exec('bzr add --no-recurse %s', $path)) { $errors .= implode("\n", drush_shell_exec_output()); } } else if (preg_match('/^\s+D\s+(.*)/', $line, $matches)) { $path = $root[0] .'/'. $matches[1]; if (!drush_shell_exec('bzr remove %s', $path)) { $errors .= implode("\n", drush_shell_exec_output()); } } } if (!empty($errors)) { return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors))); } } else { return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output())))); } return TRUE; } } /** * Automatically commit changes to the repository */ private function commit($project) { if (drush_get_option('bzrcommit')) { $message = drush_get_option('bzrmessage'); if (empty($message)) { $message = dt("Drush automatic commit.\nProject: @name @type\nCommand: @arguments", array('@name' => $project['name'], '@type' => $project['project_type'], '@arguments' => implode(' ', $_SERVER['argv']))); } if (drush_shell_exec('bzr commit --message=%s %s', $message, $project['full_project_path'])) { drush_log(dt('Project committed to Bazaar successfully'), 'ok'); } else { drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output())))); } } else { drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back.")); } } public static function reserved_files() { return array('.bzr', '.bzrignore', '.bzrtags'); } } drush-5.10.0/commands/pm/version_control/svn.inc000066400000000000000000000146041222105546100216700ustar00rootroot00000000000000 $project['full_project_path'], '!output' => implode("\n", $output)))); } } else { return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } // Check for incoming updates $args = array_keys($items_to_test); array_unshift($args, 'svn status -u '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args))); array_unshift($args, $project['full_project_path']); if (call_user_func_array('drush_shell_cd_and_exec', $args)) { $output = preg_grep('/\*/', drush_shell_exec_output()); if (!empty($output)) { return drush_set_error('DRUSH_PM_SVN_REMOTE_CHANGES', dt("The SVN working copy at !path appears to be out of date with the repository (see below). Please run 'svn update' to pull down changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); } } else { return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn remote status on !path. Check that you have connectivity to the repository.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } return TRUE; } /** * Implementation of rollback(). */ public function rollback($project) { if (drush_shell_exec('svn revert '. drush_get_option('svnrevertparams') .' '. $project['full_project_path'])) { $output = drush_shell_exec_output(); if (!empty($output)) { return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output)))); } } else { return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } } /** * Implementation of post_update(). */ public function post_update($project) { if ($this->sync($project)) { // Only attempt commit on a sucessful sync $this->commit($project); } } /** * Implementation of post_download(). */ public function post_download($project) { if ($this->sync($project)) { // Only attempt commit on a sucessful sync $this->commit($project); } } /** * Automatically add any unversioned files to Subversion and remove any files * that have been deleted on the file system */ private function sync($project) { if (drush_get_option('svnsync')) { $errors = ''; if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $project['full_project_path'])) { $output = drush_shell_exec_output(); foreach ($output as $line) { if (preg_match('/^\? *(.*)/', $line, $matches)) { if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) { $errors .= implode("\n", drush_shell_exec_output()); } } if (preg_match('/^\! *(.*)/', $line, $matches)) { if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) { $errors .= implode("\n", drush_shell_exec_output()); } } } if (!empty($errors)) { return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from this SVN working copy.\nThe specific errors are below:\n!errors", array('!errors' => $errors))); } } else { return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output())))); } return TRUE; } } /** * Automatically commit changes to the repository */ private function commit($project) { if (drush_get_option('svncommit')) { $message = drush_get_option('svnmessage'); if (empty($message)) { $message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']); } if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $project['full_project_path'])) { drush_log(dt('Project committed to Subversion successfully'), 'ok'); } else { drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output())))); } } else { drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back.")); } } public static function reserved_files() { return array('.svn'); } } drush-5.10.0/commands/runserver/000077500000000000000000000000001222105546100165545ustar00rootroot00000000000000drush-5.10.0/commands/runserver/runserver-drupal.inc000066400000000000000000000035421222105546100225730ustar00rootroot00000000000000env; // Handle static files and php scripts accessed directly $uri = $request->uri; $doc_root = DRUPAL_ROOT; $path = $doc_root . $uri; if (is_file(realpath($path))) { if (preg_match('#\.php$#', $uri)) { // SCRIPT_NAME is equal to uri if it does exist on disk $cgi_env['SCRIPT_NAME'] = $uri; return $this->get_php_response($request, $path, $cgi_env); } return $this->get_static_response($request, $path); } // Rewrite clean-urls $cgi_env['QUERY_STRING'] = 'q=' . ltrim($uri, '/'); if ($request->query_string != "") { $cgi_env['QUERY_STRING'] .= '&' . $request->query_string; } $cgi_env['SCRIPT_NAME'] = '/index.php'; $cgi_env['HTTP_HOST'] = $cgi_env['SERVER_NAME'] = $this->site; return $this->get_php_response($request, $doc_root . '/index.php', $cgi_env); } /** * Override get started event. */ function listening() { if (!empty($this->browse)) { drush_start_browser($this->browse); } } /** * Override request done event. */ function request_done($request) { drush_print(trim($this->get_log_line($request), "\n")); if ($this->debug) { drush_print_r($request); } } } drush-5.10.0/commands/runserver/runserver-prepend.php000066400000000000000000000047441222105546100227640ustar00rootroot00000000000000 strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])), '!severity' => $log_entry['severity'], '!type' => $log_entry['type'], '!ip' => $log_entry['ip'], '!request_uri' => $log_entry['request_uri'], '!referer' => $log_entry['referer'], '!uid' => $log_entry['user']->uid, '!link' => strip_tags($log_entry['link']), )); error_log($message); } } function runserver_env($key) { if (isset($_SERVER[$key])) { return $_SERVER[$key]; } else { return getenv($key); } } drush-5.10.0/commands/runserver/runserver.drush.inc000066400000000000000000000312131222105546100224260ustar00rootroot00000000000000 'Runs a lightweight built in http server for development.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, 'arguments' => array( 'addr:port/path' => 'Host IP address and port number to bind to and path to open in web browser. Format is addr:port/path, default 127.0.0.1:8888, all elements optional. See examples for shorthand.', ), 'options' => array( 'server' => 'Which http server to use - either: "cgi" for a CGI based httpserver (default, requires php 5.3 and php-cgi binary) or "builtin" for php 5.4 built in http server.', 'php-cgi' => 'Name of the php-cgi binary. If it is not on your current $PATH you should include the full path. You can include command line parameters to pass into php-cgi.', 'variables' => 'Key-value array of variables to override in the $conf array for the running site. By default disables drupal_http_request_fails to avoid errors on Windows (which supports only one connection at a time). Comma delimited list of name=value pairs (or array in drushrc).', 'default-server' => 'A default addr:port/path to use for any values not specified as an argument.', 'user' => 'If opening a web browser, automatically log in as this user (user ID or username).', 'browser' => 'If opening a web browser, which browser to user (defaults to operating system default).', 'dns' => 'Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.', ), 'aliases' => array('rs'), 'examples' => array( 'drush rs 8080' => 'Start runserver on 127.0.0.1, port 8080.', 'drush rs 10.0.0.28:80' => 'Start runserver on 10.0.0.28, port 80.', 'drush rs --php-cgi=php5-cgi --dns localhost:8888/user' => 'Start runserver on localhost (using rDNS to determine binding IP), port 8888, and open /user in browser. Use "php5-cgi" as the php-cgi binary.', 'drush rs /' => 'Start runserver on default IP/port (127.0.0.1, port 8888), and open / in browser.', 'drush rs --default-server=127.0.0.1:8080/ -' => 'Use a default (would be specified in your drushrc) that starts runserver on port 8080, and opens a browser to the front page. Set path to a single hyphen path in argument to prevent opening browser for this session.', 'drush rs --server=builtin :9000/admin' => 'Start builtin php 5.4 runserver on 127.0.0.1, port 9000, and open /admin in browser. Note that you need a colon when you specify port and path, but no IP.', ), ); return $items; } /** * Validate callback for runserver command. */ function drush_core_runserver_validate() { if (version_compare(PHP_VERSION, '5.3.0') < 0) { return drush_set_error('RUNSERVER_PHP53_VERSION', dt('The runserver command requires php 5.3, which could not be found.')); } $php_cgi = drush_shell_exec('which ' . drush_get_option('php-cgi', 'php-cgi')); $builtin = version_compare(PHP_VERSION, '5.4.0') >= 0; $server = drush_get_option('server', FALSE); if (!$server) { // No server specified, try and find a valid server option, preferring cgi. if ($php_cgi) { $server = 'cgi'; drush_set_option('server', 'cgi'); } else if ($builtin) { $server = 'builtin'; drush_set_option('server', 'builtin'); } else { return drush_set_error('RUNSERVER_PHP_CGI54', dt('The runserver command requires either the php-cgi binary, or php 5.4 (or higher). Neither could not be found.')); } } else if ($server == 'cgi' && !$php_cgi) { // Validate user specified cgi server option. return drush_set_error('RUNSERVER_PHP_CGI', dt('The runserver command with the "cgi" server requires the php-cgi binary, which could not be found.')); } else if ($server == 'builtin' && !$builtin) { // Validate user specified builtin server option. return drush_set_error('RUNSERVER_PHP_CGI', dt('The runserver command with the "builtin" server requires php 5.4 (or higher), which could not be found.')); } // Update with detected configuration. $server = drush_get_option('server', FALSE); if ($server == 'cgi') { // Fetch httpserver cgi based server to our /lib directory, if needed. $lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib'); $httpserverfile = $lib . '/' . DRUSH_HTTPSERVER_DIR_BASE . substr(DRUSH_HTTPSERVER_VERSION, 0, 7) . '/httpserver.php'; if (!drush_file_not_empty($httpserverfile)) { // Download and extract httpserver, and confirm success. drush_lib_fetch(DRUSH_HTTPSERVER_BASE_URL . DRUSH_HTTPSERVER_VERSION); if (!drush_file_not_empty($httpserverfile)) { // Something went wrong - the library is still not present. return drush_set_error('RUNSERVER_HTTPSERVER_LIB_NOT_FOUND', dt("The runserver command needs a copy of the httpserver library in order to function, and the attempt to download this file automatically failed. To continue you will need to download the package from !url, extract it into the !lib directory, such that httpserver.php exists at !httpserverfile.", array('!version' => DRUSH_HTTPSERVER_VERSION, '!url' => DRUSH_HTTPSERVER_BASE_URL . DRUSH_HTTPSERVER_VERSION, '!httpserverfile' => $httpserverfile, '!lib' => $lib))); } } } // Check we have a valid server. if (!in_array($server, array('cgi', 'builtin'))) { return drush_set_error('RUNSERVER_INVALID', dt('Invalid server specified.')); } } /** * Callback for runserver command. */ function drush_core_runserver($uri = NULL) { global $user, $base_url; // Determine active configuration. $drush_default = array( 'host' => '127.0.0.1', 'port' => '8888', 'path' => '', ); $user_default = runserver_parse_uri(drush_get_option('default-server', '127.0.0.1:8888')); $uri = runserver_parse_uri($uri) + $user_default + $drush_default; if (ltrim($uri['path'], '/') == '-') { // Allow a path of a single hyphen to clear a default path. $uri['path'] = ''; } // Remove any leading slashes from the path, since that is what url() expects. $uri['path'] = ltrim($uri['path'], '/'); // Determine and set the new URI. $hostname = $addr = $uri['host']; if (drush_get_option('dns', FALSE)) { if (ip2long($hostname)) { $hostname = gethostbyaddr($hostname); } else { $addr = gethostbyname($hostname); } } drush_set_context('DRUSH_URI', 'http://' . $hostname . ':' . $uri['port']); // We pass in the currently logged in user (if set via the --user option), // which will automatically log this user in the browser during the first // request. if (drush_get_option('user', FALSE)) { drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_LOGIN); } // We delete any registered files here, since they are not caught by Ctrl-C. _drush_delete_registered_files(); // We set the effective base_url, since we have now detected the current site, // and need to ensure generated URLs point to our runserver host. // We also pass in the effective base_url to our auto_prepend_script via the // CGI environment. This allows Drupal to generate working URLs to this http // server, whilst finding the correct multisite from the HTTP_HOST header. $base_url = 'http://' . $addr . ':' . $uri['port']; $env['RUNSERVER_BASE_URL'] = $base_url; // We pass in an array of $conf overrides using the same approach. // By default we set drupal_http_request_fails to FALSE, as the httpserver // is unable to process simultaneous requests on some systems. // This is available as an option for developers to pass in their own // favorite $conf overrides (e.g. disabling css aggregation). $current_override = drush_get_option_list('variables', array('drupal_http_request_fails' => FALSE)); foreach ($current_override as $name => $value) { if (is_numeric($name) && (strpos($value, '=') !== FALSE)) { list($name, $value) = explode('=', $value, 2); } $override[$name] = $value; } $env['RUNSERVER_CONF'] = urlencode(serialize($override)); // We log in with the specified user ID (if set) via the password reset URL. $user_message = ''; if ($user->uid) { $options = array('query' => array('destination' => $uri['path'])); $browse = url(user_pass_reset_url($user) . '/login', $options); $user_message = ', logged in as ' . $user->name; } else { $browse = url($uri['path']); } drush_print(dt('HTTP server listening on !addr, port !port (see http://!hostname:!port/!path), serving site !site!user...', array('!addr' => $addr, '!hostname' => $hostname, '!port' => $uri['port'], '!path' => $uri['path'], '!site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'), '!user' => $user_message))); if (drush_get_option('server', FALSE) == 'builtin') { // Start php 5.4 builtin server. // Store data used by runserver-prepend.php in the shell environment. foreach ($env as $key => $value) { putenv($key . '=' . $value); } if (!empty($uri['path'])) { // Start a browser if desired. Include a 2 second delay to allow the // server to come up. drush_start_browser($browse, 2); } // Start the server using 'php -S'. $php = drush_get_option('php'); drush_shell_exec_interactive($php . ' -S ' . $addr . ':' . $uri['port'] . ' --define auto_prepend_file="' . DRUSH_BASE_PATH . '/commands/runserver/runserver-prepend.php"'); } else { // Include the library and our class that extends it. $lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib'); $httpserverfile = $lib . '/' . DRUSH_HTTPSERVER_DIR_BASE . substr(DRUSH_HTTPSERVER_VERSION, 0, 7) . '/httpserver.php'; require_once $httpserverfile; require_once 'runserver-drupal.inc'; // Create a new httpserver instance and start it running. $server = new DrupalServer(array( 'addr' => $addr, 'port' => $uri['port'], 'browse' => $browse, 'hostname' => $hostname, 'site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'), 'server_id' => 'Drush runserver', 'php_cgi' => drush_get_option('php-cgi', 'php-cgi') . ' --define auto_prepend_file="' . DRUSH_BASE_PATH . '/commands/runserver/runserver-prepend.php"', 'env' => $env, 'debug' => drush_get_context('DRUSH_DEBUG'), )); $server->run_forever(); } } /** * Parse a URI or partial URI (including just a port, host IP or path). * * @param $uri * String that can contain partial URI. * @return array * URI array as returned by parse_url. */ function runserver_parse_uri($uri) { if ($uri[0] == ':') { // ':port/path' shorthand, insert a placeholder hostname to allow parsing. $uri = 'placeholder-hostname' . $uri; } $first_part = substr($uri, 0, strpos($uri, '/')); if (ip2long($first_part)) { // 'IP/path' shorthand, insert a schema to allow parsing. $uri = 'http://' . $uri; } $uri = parse_url($uri); if (empty($uri)) { return drush_set_error('RUNSERVER_INVALID_ADDRPORT', dt('Invalid argument - should be in the "host:port/path" format, numeric (port only) or non-numeric (path only).')); } if (count($uri) == 1 && isset($uri['path'])) { if (is_numeric($uri['path'])) { // Port only shorthand. $uri['port'] = $uri['path']; unset($uri['path']); } else if (ip2long($uri['path'])) { // IP only shorthand. $uri['host'] = $uri['path']; unset($uri['path']); } } if (isset($uri['host']) && $uri['host'] == 'placeholder-hostname') { unset($uri['host']); } return $uri; } drush-5.10.0/commands/sql/000077500000000000000000000000001222105546100153205ustar00rootroot00000000000000drush-5.10.0/commands/sql/sql.drush.inc000066400000000000000000001417541222105546100177520ustar00rootroot00000000000000 'The DB connection key if using multiple connections in settings.php.', 'example-value' => 'extra', ); $options['target'] = array( 'description' => 'The name of a target within the specified database.', 'example-value' => 'key', // Gets unhidden in help_alter(). We only want to show this to D7 users but have to // declare it here since some commands do not bootstrap fully. 'hidden' => TRUE, ); $db_url['db-url'] = array( 'description' => 'A Drupal 6 style database URL. Only required for initial install - not re-install.', 'example-value' => 'mysql://root:pass@127.0.0.1/db', ); $items['sql-drop'] = array( 'description' => 'Drop all tables in a given database.', 'arguments' => array( ), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'options' => array( 'yes' => 'Skip confirmation and proceed.', 'result-file' => array( 'description' => 'Save to a file. The file should be relative to Drupal root. Recommended.', 'example-value' => '/path/to/file', ), ) + $options + $db_url, 'topics' => array('docs-policy'), ); $items['sql-conf'] = array( 'description' => 'Print database connection details using print_r().', 'hidden' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'options' => array( 'all' => 'Show all database connections, instead of just one.', 'show-passwords' => 'Show database password.', ) + $options, ); $items['sql-connect'] = array( 'description' => 'A string for connecting to the DB.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'options' => $options + $db_url, 'examples' => array( '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.', ), ); $items['sql-create'] = array( 'description' => 'Create a database.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'examples' => array( 'drush sql-create' => 'Create the database for the current site.', 'drush @site.test sql-create' => 'Create the database as specified for @site.test.', 'drush sql-create --db-su=root --db-su-pw=rootpassword --db-url="mysql://drupal_db_user:drupal_db_password@127.0.0.1/drupal_db"' => 'Create the database as specified in the db-url option.' ), 'options' => array( 'db-su' => 'Account to use when creating a new database. Optional.', 'db-su-pw' => 'Password for the "db-su" account. Optional.', ) + $options + $db_url, ); $items['sql-dump'] = array( 'callback' => 'drush_sql_dump_execute', 'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'examples' => array( 'drush sql-dump --result-file=../18.sql' => 'Save SQL dump to the directory above Drupal root.', 'drush sql-dump --skip-tables-key=common' => 'Skip standard tables. @see example.drushrc.php', ), 'options' => array( 'result-file' => array( 'description' => 'Save to a file. The file should be relative to Drupal root. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.', 'example-value' => '/path/to/file', 'value' => 'optional', ), 'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.', 'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.', 'tables-key' => 'A key in the $tables array. Optional.', 'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.', 'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.', 'tables-list' => 'A comma-separated list of tables to transfer. Optional.', 'ordered-dump' => 'Use this option to output ordered INSERT statements in the sql-dump.Useful when backups are managed in a Version Control System. Optional.', 'create-db' => array('hidden' => TRUE, 'description' => 'Omit DROP TABLE statements. Postgres and Oracle only. Used by sql-sync, since including the DROP TABLE statements interfere with the import when the database is created.'), 'data-only' => 'Dump data without statements to create any of the schema.', 'ordered-dump' => 'Order by primary key and add line breaks for efficient diff in revision control. Also, faster rsync. Slows down the dump. Mysql only.', 'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.', ) + $options + $db_url, ); $items['sql-query'] = array( 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'description' => 'Execute a query against the site database.', 'examples' => array( 'drush sql-query "SELECT * FROM users WHERE uid=1"' => 'Browse user record. Table prefixes, if used, must be added to table names by hand.', 'drush sql-query --db-prefix "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored. Caution: curly-braces will be stripped from all portions of the query.', '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.', 'drush sql-query --file=example.sql' => 'Alternate way to import sql statements from a file.', ), 'arguments' => array( 'query' => 'An SQL query. Ignored if \'file\' is provided.', ), 'options' => array( 'result-file' => array( 'description' => 'Save to a file. The file should be relative to Drupal root. Optional.', 'example-value' => '/path/to/file', ), 'file' => 'Path to a file containing the SQL to be run.', 'extra' => 'Add custom options to the mysql command.', 'db-prefix' => 'Enable replacement of braces in your query.', ) + $options + $db_url, 'aliases' => array('sqlq'), ); $items['sql-sync'] = array( 'description' => 'Copy and import source database to target database. Transfers via rsync.', 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'drush dependencies' => array('core'), // core-rsync. 'examples' => array( 'drush sql-sync @prod @dev' => 'Copy the DB defined in sites/prod to the DB in sites/dev.', ), 'arguments' => array( 'from' => 'Name of subdirectory within /sites or a site-alias.', 'to' => 'Name of subdirectory within /sites or a site-alias.', ), 'options' => array( 'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.', 'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.', 'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.', 'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.', 'tables-key' => 'A key in the $tables array. Optional.', 'tables-list' => 'A comma-separated list of tables to transfer. Optional.', 'cache' => 'Skip dump if result file exists and is less than "cache" hours old. Optional; default is 24 hours.', 'no-cache' => 'Do not cache the sql-dump file.', 'no-dump' => 'Do not dump the sql database; always use an existing dump file.', 'source-db-url' => 'Database specification for source system to dump from.', 'source-remote-port' => 'Override sql database port number in source-db-url. Optional.', 'source-remote-host' => 'Remote machine to run sql-dump file on. Optional; default is local machine.', 'source-dump' => 'Path to dump file. Optional; default is to create a temporary file.', 'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.', 'source-target' => array( 'description' => 'A key within the SOURCE database identifying a particular server in the database group.', 'example-value' => 'key', // Gets unhidden in help_alter(). We only want to show to D7 users but have to // declare it here since this command does not bootstrap fully. 'hidden' => TRUE, ), 'target-db-url' => '', 'target-remote-port' => '', 'target-remote-host' => '', 'target-dump' => '', 'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.', 'target-target' => array( 'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.', 'example-value' => 'key', // Gets unhidden in help_alter(). We only want to show to D7 users but have to // declare it here since this command does not bootstrap fully. 'hidden' => TRUE, ), 'temp' => 'Use a temporary file to hold dump files. Implies --no-cache.', 'dump-dir' => 'Directory to store sql dump files in when --source-dump or --target-dump are not used. Takes precedence over --temp.', 'create-db' => 'Create a new database before importing the database dump on the target machine.', 'db-su' => array( 'description' => 'Account to use when creating a new database. Optional.', 'example-value' => 'root', ), 'db-su-pw' => array( 'description' => 'Password for the "db-su" account. Optional.', 'example-value' => 'pass', ), 'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.', 'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync. Optional.', ), 'sub-options' => array( 'sanitize' => array( 'sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".', 'sanitize-email' => 'The pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name. Default is "user+%uid@localhost".', 'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations', ), ), 'topics' => array('docs-aliases', 'docs-policy', 'docs-example-sync-via-http', 'docs-example-sync-extension'), ); $items['sql-cli'] = array( 'description' => "Open a SQL command-line interface using Drupal's credentials.", 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'options' => array( 'A' => 'Skip reading table information. This gives a quicker start of mysql.', ) + $db_url, 'aliases' => array('sqlc'), 'examples' => array( 'drush sql-cli' => "Open a SQL command-line interface using Drupal's credentials.", 'drush sql-cli -A' => "Open a SQL command-line interface using Drupal's credentials and skip reading table information.", ), ); $items['sql-sanitize'] = array( 'description' => "Run sanitization operations on the current database.", 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'hidden' => TRUE, 'options' => array( 'sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".', 'sanitize-email' => 'The pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name. Default is "user+%uid@localhost".', ) + $db_url, 'aliases' => array('sqlsan'), ); return $items; } /** * Implements hook_drush_help_alter(). */ function sql_drush_help_alter(&$command) { // Drupal 7+ only options. if (drush_drupal_major_version() >= 7) { if ($command['command'] == 'sql-sync') { unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']); } else { unset($command['options']['target']['hidden']); } } } /** * Command argument complete callback. * * @return * Array of available site aliases. */ function sql_sql_sync_complete() { return array('values' => array_keys(_drush_sitealias_all_list())); } /** * Check whether further bootstrap is needed. If so, do it. */ function drush_sql_bootstrap_further() { if (!drush_get_option('db-url')) { drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); } } /** * Command callback. Displays the Drupal site's database connection string. */ function drush_sql_conf() { // Under Drupal 7, if the database is configured but empty, then // DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION will throw an exception. // If this happens, we'll just catch it and continue. // TODO: Fix this in the bootstrap, per http://drupal.org/node/1996004 try { drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); } catch (Exception $e) { } if (drush_get_option('db-url', FALSE)) { $db_spec['db-url'] = $GLOBALS['db_url']; } elseif (drush_get_option('all', FALSE)) { $db_spec = _drush_sql_get_all_db_specs(); } if (!isset($db_spec)) { $db_spec = _drush_sql_get_db_spec(); } $return = $db_spec; if (!drush_get_option('show-passwords', FALSE)) { drush_unset_recursive($db_spec, 'password'); } drush_print_r($db_spec); return $return; } /** * Command callback. Emits a connect string for mysql or pgsql. */ function _drush_sql_connect($db_spec = NULL) { switch (_drush_sql_get_scheme($db_spec)) { case 'mysql': $command = 'mysql'; if (drush_get_option('A', FALSE)) { $command .= ' -A'; } break; case 'pgsql': $command = 'psql'; break; case 'sqlite': $command = 'sqlite3'; break; case 'sqlsrv': $command = 'sqlcmd'; break; case 'oracle': // use rlwrap if available for readline support if ($handle = popen('rlwrap -v', 'r')) { $command = 'rlwrap sqlplus'; pclose($handle); } else { $command = 'sqlplus'; } break; } $command .= _drush_sql_get_credentials($db_spec); return $command; } function drush_sql_connect() { drush_sql_bootstrap_further(); $connect = _drush_sql_connect(); drush_print($connect); return $connect; } /** * Command callback. Create a database. */ function drush_sql_create() { $db_spec = _drush_sql_get_db_spec(); // Prompt for confirmation. if (!drush_get_context('DRUSH_SIMULATE')) { $txt_destination = (isset($db_spec['remote-host']) ? $db_spec['remote-host'] . '/' : '') . $db_spec['database']; drush_print(dt("Creating database !target. Any possible existing database will be dropped!", array('!target' => $txt_destination))); if (!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } } return _drush_sql_create($db_spec); } function _drush_sql_create($db_spec) { $sql = drush_sql_build_createdb_sql($db_spec, TRUE); // Get credentials to connect to the server, but not the database which we // are about to DROP. @see _drush_sql_get_credentials(). $create_db_spec = $db_spec; unset($create_db_spec['database']); $create_db_su = drush_sql_su($create_db_spec); return _drush_sql_query($sql, $create_db_su); } /** * Command callback. Outputs the entire Drupal database in SQL format using mysqldump. */ function drush_sql_dump_execute() { drush_sql_bootstrap_further(); list($exec, $file) = drush_sql_dump(); // Avoid the php memory of the $output array in drush_shell_exec(). if (!$return = drush_op_system($exec)) { if ($file) { drush_log(dt('Database dump saved to !path', array('!path' => $file)), 'success'); } } else { return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed'); } } function drush_sql_get_table_selection() { // Skip large core tables if instructed. Also used by 'sql-sync' command. $skip_tables = _drush_sql_get_table_list('skip-tables'); // Skip any structure-tables as well. $structure_tables = _drush_sql_get_table_list('structure-tables'); // Dump only the specified tables. Takes precedence over skip-tables and structure-tables. $tables = _drush_sql_get_table_list('tables'); return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables); } /** * Build a mysqldump/pg_dump/sqlite statement. * * @param db_spec * For /D6, a $db_url. For D7+, a target in the default DB connection. * @return array * An array with items. * 1. A mysqldump/pg_dump/sqlite statement that is ready for executing. * 2. The filepath where the dump will be saved. */ function drush_sql_dump($db_spec = NULL) { return drush_sql_build_dump_command(drush_sql_get_table_selection(), $db_spec, drush_get_option('result-file', FALSE)); } /** * Build a mysqldump/pg_dump/sqlite statement. * * @param array $table_selection * Supported keys: 'skip', 'structure', 'tables'. * @param db_spec * For D5/D6, a $db_url. For D7, a target in the default DB connection. * @param file * Destination for the dump file. * @return array * An array with items. * 1. A mysqldump/pg_dump/sqlite statement that is ready for executing. * 2. The filepath where the dump will be saved. */ function drush_sql_build_dump_command($table_selection, $db_spec = NULL, $file = FALSE) { $skip_tables = $table_selection['skip']; $structure_tables = $table_selection['structure']; $tables = $table_selection['tables']; $ignores = array(); $skip_tables = array_merge($structure_tables, $skip_tables); $data_only = drush_get_option('data-only'); // The ordered-dump option is only supported by MySQL for now. // @todo add documention once a hook for drush_get_option_help() is available. // @see drush_get_option_help() in drush.inc $ordered_dump = drush_get_option('ordered-dump'); if (is_null($db_spec)) { $db_spec = _drush_sql_get_db_spec(); } $database = $db_spec['database']; // $file is passed in to us usually via --result-file. If the user // has set $options['result-file'] = TRUE, then we // will generate an SQL dump file in the same backup // directory that pm-updatecode uses. if ($file) { if ($file === TRUE) { // User did not pass a specific value for --result-file. Make one. $backup = drush_include_engine('version_control', 'backup'); $backup_dir = $backup->prepare_backup_dir($db_spec['database']); if (empty($backup_dir)) { $backup_dir = "/tmp"; } $file = $backup_dir . '/@DATABASE_@DATE.sql'; } $file = str_replace(array('@DATABASE', '@DATE'), array($database, gmdate('Ymd_His')), $file); } switch (_drush_sql_get_scheme($db_spec)) { case 'mysqli': case 'mysql': $exec = 'mysqldump'; if ($file) { $exec .= ' --result-file '. $file; } // mysqldump wants 'databasename' instead of 'database=databasename' for no good reason. // We had --skip-add-locks here for a while to help people with insufficient permissions, // but removed it because it slows down the import a lot. See http://drupal.org/node/1283978 $extra = ' --no-autocommit --single-transaction --opt -Q' . str_replace('--database=', ' ', _drush_sql_get_credentials($db_spec)); if (isset($data_only)) { $extra .= ' --no-create-info'; } if (isset($ordered_dump)) { $extra .= ' --skip-extended-insert --order-by-primary'; } $exec .= $extra; if (!empty($tables)) { $exec .= ' ' . implode(' ', $tables); } else { // Append the ignore-table options. foreach ($skip_tables as $table) { $ignores[] = "--ignore-table=$database.$table"; } $exec .= ' '. implode(' ', $ignores); // Run mysqldump again and append output if we need some structure only tables. if (!empty($structure_tables)) { $exec .= " && mysqldump --no-data $extra " . implode(' ', $structure_tables); if ($file) { $exec .= " >> $file"; } } } break; case 'pgsql': $create_db = drush_get_option('create-db'); $exec = 'pg_dump '; if ($file) { $exec .= ' --file '. $file; } // Unlike psql, pg_dump does not take a '--dbname=' before the database name. $extra = str_replace('--dbname=', ' ', _drush_sql_get_credentials($db_spec)); if (isset($data_only)) { $extra .= ' --data-only'; } $exec .= $extra; $exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : ''); if (!empty($tables)) { foreach ($tables as $table) { $exec .= " --table=$table"; } } else { foreach ($skip_tables as $table) { $ignores[] = "--exclude-table=$table"; } $exec .= ' '. implode(' ', $ignores); // Run pg_dump again and append output if we need some structure only tables. if (!empty($structure_tables)) { $schemaonlies = array(); foreach ($structure_tables as $table) { $schemaonlies[] = "--table=$table"; } $exec .= " && pg_dump --schema-only " . implode(' ', $schemaonlies) . $extra; if ($file) { $exec .= " >> $file"; } } } break; case 'sqlite': // Dumping is usually not necessary in SQLite, since all database data // is stored in a single file on the filesystem which can be copied just // like any other file. But it still has a use in migration purposes and // building human-readable diffs and such, so let's do it anyway. $exec = _drush_sql_connect($db_spec); // SQLite's dump command doesn't support many of the features of its // Postgres or MySQL equivalents. We may be able to fake some in the // future, but for now, let's just support simple dumps. $exec .= ' ".dump"'; if ($file) { $exec .= ' > '. $file; } break; case 'sqlsrv': // Change variable '$file' by reference in order to get drush_log() to report. if (!$file) { $file = $db_spec['database'] . '_' . date('Ymd_His') . '.bak'; } $exec = "sqlcmd -U \"" . $db_spec['username'] . "\" -P \"" . $db_spec['password'] . "\" -S \"" . $db_spec['host'] . "\" -Q \"BACKUP DATABASE [" . $db_spec['database'] . "] TO DISK='" . $file . "'\""; break; case 'oracle': $create_db = drush_get_option('create-db'); $exec = 'exp ' . _drush_sql_get_credentials($db_spec); // Change variable '$file' by reference in order to get drush_log() to report. if (!$file) { $file = $db_spec['username'] . '.dmp'; } $exec .= ' file=' . $file; if (!empty($tables)) $exec .= ' tables="(' . implode(',', $tables) . ')"'; else $exec .= ' owner=' . $db_spec['username']; break; } if (drush_get_option('gzip')) { if ($file) { $escfile = drush_escapeshellarg($file); if (drush_get_context('DRUSH_AFFIRMATIVE')) { // Gzip the result-file without Gzip confirmation $exec .= " && gzip -f $escfile"; $file .= '.gz'; } else { // Gzip the result-file $exec .= " && gzip $escfile"; $file .= '.gz'; } } else { // gzip via pipe since user has not specified a file. $exec .= "| gzip"; } } return array($exec, $file); } /** * Consult the specified options and return the list of tables * specified. * * @param option_name * The option name to check: skip-tables, structure-tables * or tables. This function will check both *-key and *-list, * and, in the case of sql-sync, will also check target-* * and source-*, to see if an alias set one of these options. * @returns array * Returns an array of tables based on the first option * found, or an empty array if there were no matches. */ function _drush_sql_get_table_list($option_name) { foreach(array('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) { foreach(explode(',',$prefix_list) as $prefix) { $key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context); foreach(explode(',', $key_list) as $key) { $all_tables = drush_get_option($option_name, array()); if (array_key_exists($key, $all_tables)) { return $all_tables[$key]; } if ($option_name != 'tables') { $all_tables = drush_get_option('tables', array()); if (array_key_exists($key, $all_tables)) { return $all_tables[$key]; } } } $table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context); if (isset($table_list)) { return empty($table_list) ? array() : explode(',', $table_list); } } } return array(); } /** * Command callback. Executes the given SQL query on the Drupal database. */ function drush_sql_query($query = NULL) { drush_sql_bootstrap_further(); $filename = drush_get_option('file', NULL); // Enable prefix processing when db-prefix option is used. if (drush_get_option('db-prefix')) { drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE); } $result = _drush_sql_query($query, NULL, $filename); if (!$result) { return drush_set_error('DRUSH_SQL_NO_QUERY', dt('Query failed.')); } return TRUE; } /* * Execute a SQL query. * * @param string $query * The SQL to be executed. Should be NULL if $file is provided. * @param array $db_spec * A database target. * @param string $filename * A path to a file containing the SQL to be executed. */ function _drush_sql_query($query, $db_spec = NULL, $filename = NULL) { $suffix = ''; $scheme = _drush_sql_get_scheme($db_spec); // Inject table prefixes as needed. if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) { if ($filename) { $query = file_get_contents($filename); } // Enable prefix processing which can be dangerous so off by default. See http://drupal.org/node/1219850. if (drush_get_option('db-prefix')) { if (drush_drupal_major_version() >= 7) { $query = Database::getConnection()->prefixTables($query); } else { $query = db_prefix_tables($query); } } } // is this an oracle query if ($scheme == 'oracle') { $query = drush_sql_format_oracle($query); $suffix = '.sql'; } // Convert mysql 'show tables;' query into something pgsql understands if (($scheme == 'pgsql') && ($query == 'show tables;')) { $query = drush_sql_show_tables_pgsql(); } // Save $query to a tmp file if needed. We will redirect it in. if (!$filename) { $filename = drush_save_data_to_temp_file($query, $suffix); } $exec = drush_sql_build_exec($db_spec, $filename); if ($output_file = drush_get_option('result-file')) { $exec .= ' > '. drush_escapeshellarg($output_file); } // In --simulate mode, drush_op will show the call to mysql or psql, // but the sql query itself is stored in a temp file and not displayed. // We will therefore show the query explicitly in the interest of full disclosure. if (drush_get_context('DRUSH_SIMULATE')) { drush_print('sql-query: ' . $query); if (!empty($exec)) { drush_print('exec: ' . $exec); } return TRUE; } if (empty($scheme)) { return drush_set_error('DRUSH_SQL_NO_DATABASE', dt("No database to operate on.")); } if (empty($exec)) { return drush_set_error('DRUSH_SQL_NO_QUERY', 'No query provided'); } return (drush_op_system($exec) == 0); } function drush_sql_drop() { drush_sql_bootstrap_further(); $db_spec = _drush_sql_get_db_spec(); if (!$db_spec) { return drush_set_error('DRUSH_SQL_NO_DATABASE', dt("No database to operate on.")); } if (!drush_confirm(dt('Do you really want to drop all tables in the database !db?', array('!db' => $db_spec['database'])))) { return drush_user_abort(); } _drush_sql_drop($db_spec); } // n.b. site-install uses _drush_sql_drop as a fallback technique if // drop database; create database fails. If _drush_sql_drop // is rewritten to also use that technique, it should maintain // the drop tables code here as a fallback. function _drush_sql_drop($db_spec = NULL) { // TODO: integrate with _drush_sql_get_table_list? $suffix = ''; $scheme = _drush_sql_get_scheme($db_spec); switch ($scheme) { case 'pgsql': $query = drush_sql_show_tables_pgsql(); break; case 'sqlite': $query = '.tables'; break; case 'sqlsrv': $query = 'SELECT TABLE_NAME FROM information_schema.tables'; break; case 'oracle': $query = "SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME NOT IN ('BLOBS','LONG_IDENTIFIERS')"; $suffix = '.sql'; break; default: $query = 'SHOW TABLES;'; } $filename = drush_save_data_to_temp_file($query, $suffix); $exec = drush_sql_build_exec($db_spec, $filename); // Actually run this prep query no matter if in SIMULATE. $old = drush_get_context('DRUSH_SIMULATE'); drush_set_context('DRUSH_SIMULATE', FALSE); drush_shell_exec($exec); drush_set_context('DRUSH_SIMULATE', $old); if ($tables = drush_shell_exec_output()) { if ($scheme === 'sqlite') { // SQLite's '.tables' command always outputs the table names in a column // format, like this: // table_alpha table_charlie table_echo // table_bravo table_delta table_foxtrot // …and there doesn't seem to be a way to fix that. So we need to do some // clean-up. // Since we're already doing iteration here, might as well build the SQL // too, since SQLite only wants one table per DROP TABLE command (so we have // to do "DROP TABLE foo; DROP TABLE bar;" instead of // "DROP TABLE foo, bar;"). $sql = ''; foreach ($tables as $line) { preg_match_all('/[^\s]+/', $line, $matches); if (!empty($matches[0])) { foreach ($matches[0] as $match) { $sql .= "DROP TABLE {$match};"; } } } // We can't use drush_op('db_query', $sql) because it will only perform one // SQL command and we're technically performing several. $exec = _drush_sql_connect($db_spec); $exec .= " '{$sql}'"; return drush_op_system($exec) == 0; } elseif ($scheme === 'sqlsrv') { // Shift off the header of the column of data returned. array_pop($tables); array_pop($tables); $sql = 'DROP TABLE '. implode(', ', $tables); return _drush_sql_query($sql, $db_spec); } else { // Shift off the header of the column of data returned. array_shift($tables); $sql = 'DROP TABLE '. implode(', ', $tables); return _drush_sql_query($sql, $db_spec); } } else { drush_log(dt('No tables to drop.'), 'ok'); } return TRUE; } function drush_sql_cli() { drush_sql_bootstrap_further(); drush_shell_proc_open(_drush_sql_connect()); } /** * Command callback. Run's the sanitization operations on the current database. */ function drush_sql_sanitize() { if (!drush_confirm(dt('Do you really want to sanitize the current database?'))) { return drush_user_abort(); } drush_sql_bootstrap_further(); drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql'); drush_command_invoke_all('drush_sql_sync_sanitize', 'default'); $options = drush_get_context('post-sync-ops'); if (!empty($options)) { if (!drush_get_context('DRUSH_SIMULATE')) { $messages = _drush_sql_get_post_sync_messages(); if ($messages) { drush_print(); drush_print($messages); } } } $sanitize_query = ''; foreach($options as $id => $data) { $sanitize_query .= $data['query'] . " "; } if ($sanitize_query) { if (!drush_get_context('DRUSH_SIMULATE')) { drush_sql_query($sanitize_query); } else { drush_print("Executing: $sanitize_query"); } } } ////////////////////////////////////////////////////////////////////////////// // SQL SERVICE HELPERS /** * Get a database specification for the active DB connection. Honors the * 'database' and 'target command' line options. Honors a --db-url option. * * @return * An info array describing a database target. */ function _drush_sql_get_db_spec() { $database = drush_get_option('database', 'default'); $target = drush_get_option('target', 'default'); if ($url = drush_get_option('db-url')) { $url = is_array($url) ? $url[$database] : $url; $db_spec = drush_convert_db_from_db_url($url); $db_spec['db_prefix'] = drush_get_option('db-prefix'); return $db_spec; } elseif (($databases = drush_get_option('databases')) && (array_key_exists($database, $databases)) && (array_key_exists($target, $databases[$database]))) { return $databases[$database][$target]; } elseif (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) { switch (drush_drupal_major_version()) { case 6: if ($url = isset($GLOBALS['db_url']) ? $GLOBALS['db_url'] : drush_get_option('db-url', NULL)) { $url = is_array($url) ? $url[$database] : $url; $db_spec = drush_convert_db_from_db_url($url); $db_spec['db_prefix'] = isset($GLOBALS['db_prefix']) ? $GLOBALS['db_prefix'] : drush_get_option('db-prefix', NULL); return $db_spec; } return NULL; default: // We don't use DB API here `sql-sync` would have to messily addConnection. if (!isset($GLOBALS['databases']) || !array_key_exists($database, $GLOBALS['databases']) || !array_key_exists($target, $GLOBALS['databases'][$database])) { return NULL; } return $GLOBALS['databases'][$database][$target]; } } } function _drush_sql_get_all_db_specs() { switch (drush_drupal_major_version()) { case 6: if (!isset($GLOBALS['db_url'])) { return NULL; } return drush_sitealias_convert_db_from_db_url($GLOBALS['db_url']); default: if (!isset($GLOBALS['databases'])) { return NULL; } return $GLOBALS['databases']; } } function _drush_sql_get_spec_from_options($prefix, $default_to_self = TRUE) { $db_spec = NULL; $databases = drush_get_option($prefix . 'databases'); if (isset($databases) && !empty($databases)) { $database = drush_get_option($prefix . 'database', 'default'); $target = drush_get_option($prefix . 'target', 'default'); if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) { $db_spec = $databases[$database][$target]; } } else { $db_url = drush_get_option($prefix . 'db-url'); if (isset($db_url)) { $db_spec = drush_convert_db_from_db_url($db_url); } elseif ($default_to_self) { $db_spec = _drush_sql_get_db_spec(); } } if (isset($db_spec)) { $remote_host = drush_get_option($prefix . 'remote-host'); if (!drush_is_local_host($remote_host)) { $db_spec['remote-host'] = $remote_host; $db_spec['port'] = drush_get_option($prefix . 'remote-port', (isset($db_spec['port']) ? $db_spec['port'] : NULL)); } } return $db_spec; } /** * Determine where to store an sql dump file. This * function is called by sql-sync. */ function drush_sql_dump_file(&$site_record) { $site_record['dump-is-temp'] = FALSE; // If the user has set the --{prefix}-dump option, then // use the exact name provided. $dump_file = drush_sitealias_get_path_option($site_record, 'dump'); if (!isset($dump_file)) { $databases = sitealias_get_databases_from_record($site_record); if (isset($databases)) { $db_spec = $databases['default']['default']; // Make a base filename pattern to use to name the dump file $filename_pattern = $db_spec['database']; if (isset($db_spec['remote-host'])) { $filename_pattern = $db_spec['remote-host'] . '_' . $filename_pattern; } } // If the user has set the --dump-dir option, then // store persistant sql dump files there. $dump_dir = drush_sitealias_get_path_option($site_record, 'dump-dir'); if (!isset($dump_dir)) { // If this is a remote site, try to find a writable tmpdir. if (isset($site_record['remote-host'])) { $result = drush_invoke_process($site_record, 'ev', array('drush_print(drush_find_tmp())'), array(), array('integrate' => FALSE, 'override-simulated' => TRUE)); // If the call to invoke process does not work for some reason // (e.g. drush not installed on the target machine), // then we will just presume that the tmp dir is '/tmp'. if (!$result || empty($result['output'])) { $dump_dir = '/tmp'; } else { $dump_dir = trim($result['output']); } $dump_file = $dump_dir . '/' . $filename_pattern . '.sql'; } else { $dump_file = drush_tempnam($filename_pattern . '.sql.'); } $site_record['dump-is-temp'] = TRUE; } else { $dump_file = $dump_dir . '/' . $filename_pattern . '.sql'; } } return $dump_file; } function _drush_sql_get_scheme($db_spec = NULL) { if (is_null($db_spec)) { $db_spec = _drush_sql_get_db_spec(); } return $db_spec['driver']; } /** * Build a fragment containing credentials and mysql-connection parameters. * * @param $db_spec * @return string */ function _drush_sql_get_credentials($db_spec = NULL) { if (is_null($db_spec)) { $db_spec = _drush_sql_get_db_spec(); } // Build an array of key-value pairs for the parameters. $parameters = array(); switch (_drush_sql_get_scheme($db_spec)) { case 'mysql': // Some drush commands (e.g. site-install) want to connect to the // server, but not the database. Connect to the built-in database. $parameters['database'] = empty($db_spec['database']) ? 'information_schema' : $db_spec['database']; // Default to unix socket if configured. if (!empty($db_spec['unix_socket'])) { $parameters['socket'] = $db_spec['unix_socket']; } // EMPTY host is not the same as NO host, and is valid (see unix_socket). elseif (isset($db_spec['host'])) { $parameters['host'] = $db_spec['host']; } if (!empty($db_spec['port'])) { $parameters['port'] = $db_spec['port']; } // User is required. Drupal calls it 'username'. MySQL calls it 'user'. $parameters['user'] = $db_spec['username']; // EMPTY password is not the same as NO password, and is valid. if (isset($db_spec['password'])) { $parameters['password'] = $db_spec['password']; } break; case 'pgsql': // Some drush commands (e.g. site-install) want to connect to the // server, but not the database. Connect to the built-in database. $parameters['dbname'] = empty($db_spec['database']) ? 'template1' : $db_spec['database']; // Host and port are optional but have defaults. $parameters['host'] = empty($db_spec['host']) ? 'localhost' : $db_spec['host']; $parameters['port'] = empty($db_spec['port']) ? '5432' : $db_spec['port']; // Username is required. $parameters['username'] = $db_spec['username']; // Don't set the password. // @see http://drupal.org/node/438828 break; case 'sqlite': // SQLite doesn't do user management, instead relying on the filesystem // for that. So the only info we really need is the path to the database // file, and not as a "--key=value" parameter. return ' ' . $db_spec['database']; break; case 'sqlsrv': // Some drush commands (e.g. site-install) want to connect to the // server, but not the database. Connect to the built-in database. $database = empty($db_spec['database']) ? 'master' : $db_spec['database']; // Host and port are optional but have defaults. $host = empty($db_spec['host']) ? '.\SQLEXPRESS' : $db_spec['host']; return ' -S ' . $host . ' -d ' . $database . ' -U ' . $db_spec['username'] . ' -P ' . $db_spec['password']; break; case 'oracle': // Return an Oracle connection string return ' ' . $db_spec['username'] .'/' . $db_spec['password'] . ($db_spec['host']=='USETNS' ? '@' . $db_spec['database'] : '@//' . $db_spec['host'] . ':' . ($db_spec['port'] ? $db_spec['port'] : '1521') . '/' . $db_spec['database']); break; } // Turn each parameter into a valid parameter string. $parameter_strings = array(); foreach ($parameters as $key => $value) { // Only escape the values, not the keys or the rest of the string. $value = drush_escapeshellarg($value); $parameter_strings[] = "--$key=$value"; } // Join the parameters and return. return ' ' . implode(' ', $parameter_strings); } function _drush_sql_get_invalid_url_msg($db_spec = NULL) { if (is_null($db_spec)) { $db_spec = _drush_sql_get_db_spec(); } switch (drush_drupal_major_version()) { case 6: return dt('Unable to parse DB connection string'); default: return dt('Unable to parse DB connection array'); } } /** * Call from a pre-sql-sync hook to register an sql * query to be executed in the post-sql-sync hook. * @see drush_sql_pre_sql_sync() and @see drush_sql_post_sql_sync(). * * @param $id * String containing an identifier representing this * operation. This id is not actually used at the * moment, it is just used to fufill the contract * of drush contexts. * @param $message * String with the confirmation message that describes * to the user what the post-sync operation is going * to do. This confirmation message is printed out * just before the user is asked whether or not the * sql-sync operation should be continued. * @param $query * String containing the sql query to execute. If no * query is provided, then the confirmation message will * be displayed to the user, but no action will be taken * in the post-sync hook. This is useful for drush modules * that wish to provide their own post-sync hooks to fix * up the target database in other ways (e.g. through * Drupal APIs). */ function drush_sql_register_post_sync_op($id, $message, $query = NULL) { $options = drush_get_context('post-sync-ops'); $options[$id] = array('message' => $message, 'query' => $query); drush_set_context('post-sync-ops', $options); } /** * Builds a confirmation message for all post-sync operations. * * @return string * All post-sync operation messages concatenated together. */ function _drush_sql_get_post_sync_messages() { $messages = FALSE; $options = drush_get_context('post-sync-ops'); if (!empty($options)) { $messages = dt('The following post-sync operations will be done on the destination:') . "\n"; foreach($options as $id => $data) { $messages .= " * " . $data['message'] . "\n"; } } return $messages; } // Convert mysql 'show tables;' query into something pgsql understands. function drush_sql_show_tables_pgsql() { return "select tablename from pg_tables where schemaname='public';"; } // Format queries to work with Oracle and SqlPlus function drush_sql_format_oracle($query) { // remove trailing semicolon from query if we have it $query = preg_replace('/\;$/', '', $query); // some sqlplus settings $settings[] = "set TRIM ON"; $settings[] = "set FEEDBACK OFF"; $settings[] = "set UNDERLINE OFF"; $settings[] = "set PAGES 0"; $settings[] = "set PAGESIZE 50000"; // are we doing a describe ? if (!preg_match('/^ *desc/i', $query)) { $settings[] = "set LINESIZE 32767"; } // are we doing a show tables ? if (preg_match('/^ *show tables/i', $query)) { $settings[] = "set HEADING OFF"; $query = "select object_name from user_objects where object_type='TABLE' order by object_name asc"; } // create settings string $sqlp_settings = implode("\n", $settings)."\n"; // important for sqlplus to exit correctly return "${sqlp_settings}${query};\nexit;\n"; } /* * Drop all tables (if DB exists) or CREATE target database. * * return boolean * TRUE or FALSE depending on success. */ function drush_sql_empty_db($db_spec = NULL) { if (is_null($db_spec)) { $db_spec = _drush_sql_get_db_spec(); } if (drush_sql_db_exists($db_spec)) { _drush_sql_drop($db_spec); } else { _drush_sql_create($db_spec); } } /* * Build DB connection array with superuser credentials if provided. * * The options 'db-su' and 'db-su-pw' will be retreived from the * specified site alias record, if it exists and contains those items. * If it does not, they will be fetched via drush_get_option. * * Note that in the context of sql-sync, the site alias record will * be taken from the target alias (e.g. `drush sql-sync @source @target`), * which will be overlayed with any options that begin with 'target-'; * therefore, the commandline options 'target-db-su' and 'target-db-su-pw' * may also affect the operation of this function. */ function drush_sql_su($db_spec, $site_alias_record = NULL) { $create_db_target = $db_spec; $create_db_target['database'] = ''; $db_superuser = drush_sitealias_get_option($site_alias_record, 'db-su'); if (isset($db_superuser)) { $create_db_target['username'] = $db_superuser; } $db_su_pw = drush_sitealias_get_option($site_alias_record, 'db-su-pw'); // If --db-su-pw is not provided and --db-su is, default to empty password. // This way db cli command will take password from .my.cnf or .pgpass. if (!empty($db_su_pw)) { $create_db_target['password'] = $db_su_pw; } elseif (isset($db_superuser)) { unset($create_db_target['password']); } return $create_db_target; } /* * Build a SQL string for dropping and creating a database. * * @param array $db_spec * A database specification array. * * @param boolean $quoted * Quote the database name. Mysql uses backticks to quote which can cause problems * in a Windows shell. Set TRUE if the CREATE is not running on the bash command line. */ function drush_sql_build_createdb_sql($db_spec, $quoted = FALSE) { $sql = array(); switch (_drush_sql_get_scheme($db_spec)) { case 'mysql': $dbname = $quoted ? '`' . $db_spec['database'] . '`' : $db_spec['database']; $sql[] = sprintf('DROP DATABASE IF EXISTS %s;', $dbname); $sql[] = sprintf('CREATE DATABASE %s /*!40100 DEFAULT CHARACTER SET utf8 */;', $dbname); $sql[] = sprintf('GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@\'%s\'', $dbname, $db_spec['username'], $db_spec['host']); $sql[] = sprintf("IDENTIFIED BY '%s';", $db_spec['password']); $sql[] = 'FLUSH PRIVILEGES;'; break; case 'pgsql': $dbname = $quoted ? '"' . $db_spec['database'] . '"' : $db_spec['database']; $sql[] = sprintf('drop database if exists %s;', $dbname); $sql[] = sprintf("create database %s ENCODING 'UTF8';", $dbname); break; } return implode(' ', $sql); } /* * Does specified database exist on target server * * @return boolean */ function drush_sql_db_exists($db_spec) { if ($db_spec['driver'] == 'sqlite') { return file_exists($db_spec['database']); } $connect_yes_db = _drush_sql_connect($db_spec); $database = $db_spec['database']; unset($db_spec['database']); $connect_no_db = _drush_sql_connect($db_spec); // We need the output back so we can't use drush_sql_query(). switch ($db_spec['driver']) { case 'mysql': $sql = "SELECT 1;"; // Suppress ugly output. Redirect STDERR and STDOUT. We just need exit code. $bit_bucket = drush_bit_bucket(); return drush_shell_exec("$connect_yes_db -e \"$sql\" 2> $bit_bucket > $bit_bucket"); case 'pgsql': $sql = "SELECT 1 AS result FROM pg_database WHERE datname='$database'"; drush_shell_exec("$connect_no_db -t -c \"$sql\""); $output = drush_shell_exec_output(); return (bool)$output[0]; case 'sqlsrv': // TODO: untested, but the gist is here. $sql = "if db_id('$database') IS NOT NULL print 1"; drush_shell_exec("$connect_no_db -Q \"$sql\""); $output = drush_shell_exec_output(); return $output[0] == 1; } } function drush_sql_build_exec($db_spec, $filepath) { $scheme = _drush_sql_get_scheme($db_spec); $exec = ''; switch ($scheme) { case 'mysql': $exec = 'mysql'; $exec .= _drush_sql_get_credentials($db_spec); $exec .= ' ' . drush_get_option('extra'); $exec .= " < " . drush_escapeshellarg($filepath); break; case 'pgsql': $exec = 'psql -q '; $exec .= _drush_sql_get_credentials($db_spec); $exec .= ' ' . (drush_get_option('extra') ? drush_get_option('extra') : "--no-align --field-separator='\t' --pset footer=off"); $exec .= " --file " . drush_escapeshellarg($filepath); break; case 'sqlite': $exec = 'sqlite3'; $exec .= ' ' . drush_get_option('extra'); $exec .= _drush_sql_get_credentials($db_spec); $exec .= " < " . drush_escapeshellarg($filepath); break; case 'sqlsrv': $exec = 'sqlcmd'; $exec .= ' ' . drush_get_option('extra'); $exec .= _drush_sql_get_credentials($db_spec); $exec .= ' -h -1 -i "' . $filepath . '"'; break; case 'oracle': $exec = 'sqlplus'; $exec .= ' ' . drush_get_option('extra'); $exec .= _drush_sql_get_credentials($db_spec); $exec .= " @" . drush_escapeshellarg($filepath); break; } return $exec; } drush-5.10.0/commands/sql/sync.sql.inc000066400000000000000000000623071222105546100175750ustar00rootroot00000000000000= 7) { $core = DRUSH_DRUPAL_CORE; include_once $core . '/includes/password.inc'; include_once $core . '/includes/bootstrap.inc'; $hash = user_hash_password($newpassword); $pw_op = "'$hash'"; } if (!empty($pw_op)) { $user_table_updates[] = "pass = $pw_op"; $message_list[] = "passwords"; } } // Sanitize email addresses. $newemail = drush_get_option(array('sanitize-email', 'destination-sanitize-email'), 'user+%uid@localhost'); if ($newemail != 'no') { if (strpos($newemail, '%') !== FALSE) { // We need a different sanitization query for Postgres and Mysql. $db_driver = $databases['default']['default']['driver']; if ($db_driver == 'pgsql') { $email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '"); $newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'"; } else { $email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '"); $newmail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')"; } $user_table_updates[] = "mail = $newmail, init = $newmail"; } else { $user_table_updates[] = "mail = '$newemail', init = '$newmail'"; } $message_list[] = 'email addresses'; } if (!empty($user_table_updates)) { $sanitize_query = "UPDATE users SET " . implode(', ', $user_table_updates) . " WHERE uid > 0;"; drush_sql_register_post_sync_op('user-email', dt('Reset !message in user table', array('!message' => implode(' and ', $message_list))), $sanitize_query); } // Seems quite portable (SQLite?) - http://en.wikipedia.org/wiki/Truncate_(SQL) $sql_sessions = 'TRUNCATE TABLE sessions;'; drush_sql_register_post_sync_op('sessions', dt('Truncate Drupal\'s sessions table'), $sql_sessions); } function drush_sql_sync($source = NULL, $destination = NULL) { $source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-'); $destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-'); $source_os = drush_os($source_settings); $target_os = drush_os($destination_settings); // Check to see if this is an sql-sync multiple command (multiple sources and multiple destinations) $is_multiple = drush_do_multiple_command('sql-sync', $source_settings, $destination_settings); if ($is_multiple === FALSE) { // Get the options for the source and target databases $source_db_url = drush_sitealias_get_db_spec($source_settings, FALSE, 'source-'); // The host may have special ssh requirements $source_remote_ssh_options = drush_sitealias_get_option($source_settings, 'ssh-options'); // rsync later will also have to know this option $source_rsync_options = array('ssh-options' => $source_remote_ssh_options); $target_db_url = drush_sitealias_get_db_spec($destination_settings, FALSE, 'target-'); // The host may have special ssh requirements $target_remote_ssh_options = drush_sitealias_get_option($destination_settings, 'ssh-options'); // rsync later will also have to know this option $target_rsync_options = array('ssh-options' => $target_remote_ssh_options); if (empty($source_db_url)) { if (empty($source_settings)) { return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for !source', array('!source' => $source))); } return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for !source', array('!source' => $source))); } if (empty($target_db_url)) { if (empty($destination_settings)) { return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for !destination', array('!destination' => $destination))); } return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for !destination', array('!destination' => $destination))); } // Set up the result file and the remote file. // If the result file is not set, then create a temporary file. // If the remote file is not set, use the same name for the remote // and local files and hope for the best. $source_dump = drush_sql_dump_file($source_settings); $target_dump = drush_sql_dump_file($destination_settings); $use_temp_files = drush_get_option('temp'); // Only use one dump file if both the source and the target are on the local machine if (!isset($source_db_url['remote-host']) && !isset($target_db_url['remote-host'])) { if ((!$destination_settings['dump-is-temp']) && ($source_settings['dump-is-temp'])) { $source_dump = $target_dump; $source_settings['dump-is-temp'] = FALSE; } else { $target_dump = $source_dump; $destination_settings['dump-is-temp'] = $source_settings['dump-is-temp']; } $local_file = $source_dump; } else { // If one of the systems is remote, then set the --remove-source-files // rsync option if the source dump file is temporary. This will get // rsync to clean up after us automatically; useful if the source is remote. if ($source_settings['dump-is-temp']) { $source_rsync_options['remove-source-files'] = TRUE; } // Set $local_file to whichever side of the operation is local, or make // a temporary file if both source and target are remote. if (!isset($source_db_url['remote-host'])) { $local_file = $source_dump; } elseif (!isset($target_db_url['remote-host'])) { $local_file = $target_dump; } else { $local_file = drush_tempnam($source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.'); } } // If source is remote, then use ssh to dump the database and then rsync to local machine // If source is local, call drush_sql_dump to dump the database to local machine // In either case, the '--no-dump' option will cause the sql-dump step to be skipped, and // we will import from the existing local file (first using rsync to fetch it if it does not exist) // // No dump affects both local and remote sql-dumps; it prevents drush sql-sync // from calling sql-dump when the local cache file is newer than the cache threshhold // No sync affects the remote sql-dump; it will prevent drush sql-sync from // rsyncing the local sql-dump file with the remote sql-dump file. $no_sync = drush_sitealias_get_option($source_settings, 'no-sync'); $no_dump = drush_sitealias_get_option($source_settings, 'no-dump'); $no_cache = drush_sitealias_get_option($source_settings, 'no-cache'); if (!isset($no_cache)) { $cache = drush_sitealias_get_option($source_settings, 'cache'); if (!isset($cache)) { $cache = 24; // Default cache is 24 hours if nothing else is specified. } } // If the 'cache' option is set, then we will set the no-dump option iff the // target file exists and its modification date is less than "cache" hours. // If --no-sync or --no-dump are already set, then this check is unnecessary. if (isset($cache) && !isset($no_sync) && !isset($no_dump)) { if (file_exists($local_file) && (filesize($local_file) > 0)) { if ((time() - filemtime($local_file)) < ($cache * 60 * 60)) { drush_log(dt('Modification time of local dump file !file is less than !cache hours old. Use the --no-cache option to force a refresh.', array('!file' => $local_file, '!cache' => $cache)), 'warning'); $no_dump = TRUE; $no_sync = TRUE; } else { drush_log(dt('Local sql cache file exists but is greater than !cache hours old.', array('!cache' => $cache))); } } else { drush_log('Local sql cache file does not exist.'); } } $table_selection = array(); if (!isset($no_dump)) { $table_selection = drush_sql_get_table_selection(); } // Prompt for confirmation. This is destructive. if (!drush_get_context('DRUSH_SIMULATE')) { // Check to see if we are using a temporary file in a situation // where the user did not specify "--temp". if (($source_settings['dump-is-temp'] || $destination_settings['dump-is-temp']) && (!isset($use_temp_files)) && (isset($source_db_url['remote-host']) || isset($target_db_url['remote-host']))) { drush_print(dt('WARNING: Using temporary files to store and transfer sql-dump. It is recommended that you specify --source-dump and --target-dump options on the command line, or set \'%dump\' or \'%dump-dir\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.')); } if (array_key_exists('tables', $table_selection) && (count($table_selection['tables']) > 0)) { drush_print(); drush_print(dt(' Only the following tables will be transferred: !list', array('!list' => implode(',', $table_selection['tables'])))); } elseif (!empty($table_selection)) { $skip_tables_list = implode(',', $table_selection['skip'] + $table_selection['structure']); if(!empty($skip_tables_list)) { drush_print(); drush_print(dt(' The following tables will be skipped: !list', array('!list' => $skip_tables_list))); } } // If there are multiple destinations, then // prompt once here and suppress the warning message // and the normal confirmation below. if (array_key_exists('site-list', $destination_settings)) { drush_print(); drush_print(dt('You are about to sync the database from !source, overwriting all of the following targets:', array('!source' => $source))); foreach ($destination_settings['site-list'] as $one_destination) { drush_print(dt(' !target', array('!target' => $one_destination))); } } else { drush_print(); $txt_source = (isset($source_db_url['remote-host']) ? $source_db_url['remote-host'] . '/' : '') . $source_db_url['database']; $txt_destination = (isset($target_db_url['remote-host']) ? $target_db_url['remote-host'] . '/' : '') . $target_db_url['database']; drush_print(dt("You will destroy data in !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination))); } // If any sanitization operations are to be done, then get the // sanitization messages and print them as part of the confirmation. // If --sanitize was specified but there were no sanitize messages, // then warn that sanitization operations will be accumulated and // processed after the sync completes. $messages = _drush_sql_get_post_sync_messages(); if ($messages) { drush_print(); drush_print($messages); } else if (drush_get_option('deferred-sanitization', FALSE) && !drush_get_option('confirm-sanitizations', FALSE)) { drush_print(); drush_print("WARNING: --sanitize was specified, but deferred (e.g. the source site is remote). The sanitization operations will be determined after the database is copied to the local system and will be run without further confirmation. Run with --confirm-sanitizations to force confirmation after the sync."); } // TODO: actually make the backup if desired. drush_print(); drush_print(dt("You might want to make a backup first, using the sql-dump command.\n")); if (!drush_confirm(dt('Do you really want to continue?'))) { return drush_user_abort(); } } if (!isset($no_dump)) { if (isset($source_db_url['remote-host'])) { $source_remote_user = ''; $source_at = ''; if (array_key_exists('remote-user', $source_settings)) { $source_remote_user = $source_settings['remote-user']; $source_at ='@'; $source_remote_pass = array_key_exists('remote-pass', $source_settings) ? ':' . $source_settings['remote-pass'] : ''; } $source_intermediate = $source_dump; $mv_intermediate = ''; // If we are doing a remote dump and the source is not a temporary file, // then first dump to a temporary file and move it to the specified file after // the dump is complete. This will reduce contention during simultaneous dumps // from different users sharing the same dump file. if (!drush_is_windows($source_os) && (!$source_settings['dump-is-temp'])) { $source_intermediate = $source_dump . '-' . date("U"); $mv_intermediate = '&& mv -f ' . $source_intermediate . ' ' . $source_dump; } list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url, $source_intermediate); $dump_exec .= $mv_intermediate; if (!drush_is_windows($source_os) && isset($cache) && !$source_settings['dump-is-temp']) { // Inject some bash commands to remotely test the modification date of the target file // if the cache option is set. $dump_exec = 'if [ ! -s ' . $source_dump . ' ] || [ $((`date "+%s"`-`stat --format="%Y" ' . $source_dump . '`)) -gt ' . ($cache * 60 * 60) . ' ] ; then ' . $dump_exec . '; fi'; } } else { list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url, $local_file); $no_sync = TRUE; } // Wrap the dump command in a remote call if the source site is remote. $dump_exec = _drush_backend_generate_command($source_settings, $dump_exec); } // Call sql-dump, either on the local machine or remotely via ssh, as appropriate. if (!empty($dump_exec)) { if (drush_op_system($dump_exec)) { return drush_set_error('DRUSH_SQL_DUMP_FAIL', 'Database dump failed'); } } // If the sql-dump was remote, then rsync the file over to the local machine. if (!isset($no_sync)) { // If the source file is a temporary file, then we will have rsync // delete it for us (remove-source-files option set above). if (!drush_core_call_rsync($source_remote_user . $source_at . $source_db_url['remote-host'] . ':' . $source_dump, drush_correct_absolute_path_for_exec($local_file, "RSYNC"), $source_rsync_options)) { return FALSE; } } // We will handle lists of destination sites differently from // single source-to-destination syncs. if (array_key_exists('site-list', $destination_settings)) { // Insure that we will not dump the source sql database // repeatedly, but will instead re-use it each time through // the redispatch loop. drush_set_option('no-dump', TRUE); drush_set_option('no-sync', TRUE); drush_set_option('source-dump', $source_dump); // Call sql-sync for each destination to push the $source_dump // to each target in turn. foreach ($destination_settings['site-list'] as $one_destination) { drush_do_command_redispatch('sql-sync', array($source, $one_destination)); } } else { // Prior to database import, we will generate a "create database" command // if the '--create-db' option was specified. Note that typically the // web server user will not have permissions to create a database; to specify // a different user to use with the create db command, the '--db-su' option // may be used. // Under postgres, "alter role username with createdb;" will give create database // permissions to the specified user if said user was not created with this right. $pre_import_commands = ''; $create_db = drush_sitealias_get_option($destination_settings, 'create-db'); if (isset($create_db)) { $create_db_su = drush_sql_su($target_db_url, $destination_settings); $db_su_connect = _drush_sql_connect($create_db_su); $pre_import_sql = drush_sql_build_createdb_sql($target_db_url); $pre_import_commands = sprintf('echo "%s" | %s; ', $pre_import_sql, $db_su_connect); // Linux requires quotes around echo statements. Windows generally supports // quotes for echo statements, but will complain if they are present // when piping echo statements as input to other commands. Also, // Windows multi-commands normally work when separated by '&&', '&', or ';' // but will not function with ';' in this case.(http://drupal.org/node/1957020) if (drush_is_windows($target_os)) { $pre_import_commands = sprintf('echo %s | %s && ', $pre_import_sql, $db_su_connect); } else { $pre_import_commands = sprintf('echo "%s" | %s; ', $pre_import_sql, $db_su_connect); } } // Generate the import command $import_command = _drush_sql_connect($target_db_url); switch (_drush_sql_get_scheme($target_db_url)) { case 'mysql': $import_command .= ' --silent'; break; case 'pgsql': $import_command .= ' -q'; break; } // If destination is remote, then use rsync to push the database, then use ssh to import the database // If destination is local, then just import the database locally if (isset($target_db_url['remote-host'])) { $target_remote_user = ''; $target_at = ''; if (array_key_exists('remote-user', $destination_settings)) { $target_remote_user = $destination_settings['remote-user']; $target_at ='@'; $target_remote_pass = array_key_exists('remote-pass', $destination_settings) ? ':' . $destination_settings['remote-pass'] : ''; } if (!drush_core_call_rsync(drush_correct_absolute_path_for_exec($local_file, "RSYNC"), $target_remote_user . $target_at . $target_db_url['remote-host'] . ':' . $target_dump, $target_rsync_options)) { return FALSE; } $import_exec = $pre_import_commands . $import_command . ' < ' . drush_escapeshellarg($target_dump, $target_os); // Delete the remote target file if it is a temporary file. if (!drush_is_windows($target_os) && $destination_settings['dump-is-temp']) { $import_exec .= '; rm -f ' . drush_escapeshellarg($target_dump, $target_os); } // TODO: make sure that the remote tmp file is deleted on remote Windows machines. } else { $import_exec = $pre_import_commands . $import_command . ' < ' . drush_escapeshellarg($local_file); } $import_exec = _drush_backend_generate_command($destination_settings, $import_exec); drush_op_system($import_exec); // After the database is imported into the destination, we // will check and see if we did not collect sanitization // operations in drush_sql_sync_init (i.e. because the source // site was remote), and if the destination site is local, // then we will call the sanitization hooks now. // This presumes an important precondition, that the code // files were sync'ed before the database was sync'ed. if (drush_get_option('deferred-sanitization', FALSE) && (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_SITE) == FALSE)) { $bootstrapped = drush_bootstrap_max_to_sitealias($destination_settings); if ($bootstrapped) { drush_command_invoke_all('drush_sql_sync_sanitize', $destination); } } } } } /** * Apply all post-sync operations that were registered in any pre-sync hook. * Follow the pattern of this function to make your own post-sync hook. * If changing the database, be sure to also include a pre-sync hook to * notify the user of the change that will be made. @see drush_sql_pre_sql_sync(). */ function drush_sql_post_sql_sync($source = NULL, $destination = NULL) { $options = drush_get_context('post-sync-ops'); if (!empty($options)) { // If 'deferred-sanitization' is set, then we collected the // sanitization operations -after- the database sync, which // means they were not confirmed up-front. We will show the // operations here, but we will not offer an opportunity to // confirm unless --confirm-sanitizations is specified. if (drush_get_option('deferred-sanitization', FALSE) || drush_get_option('confirm-sanitizations', FALSE)) { if (!drush_get_context('DRUSH_SIMULATE')) { $messages = _drush_sql_get_post_sync_messages(); if ($messages) { drush_print(); drush_print($messages); if (drush_get_option('confirm-sanitizations', FALSE)) { if (!drush_confirm(dt('Do you really want to sanitize?'))) { // Do not abort or return FALSE; that would trigger a rollback. // Just skip the sanitizations and signal that all is ok. drush_log(dt('Sanitizations skipped.'), 'ok'); return TRUE; } } } } } $destination_settings = drush_sitealias_get_record($destination); $sanitize_query = ''; foreach($options as $id => $data) { $sanitize_query .= $data['query'] . " "; } if ($sanitize_query) { if (!drush_get_context('DRUSH_SIMULATE')) { $result = drush_invoke_process($destination_settings, "sql-query", array($sanitize_query)); } else { drush_print("Executing on $destination: $sanitize_query"); } } } } drush-5.10.0/commands/user/000077500000000000000000000000001222105546100154775ustar00rootroot00000000000000drush-5.10.0/commands/user/user.drush.inc000066400000000000000000000506621222105546100203050ustar00rootroot00000000000000 'drush_user_information', 'description' => 'Print information about the specified user(s).', 'aliases' => array('uinf'), 'examples' => array( 'drush user-information 2,3,someguy,somegal,billgates@microsoft.com' => 'Display information about any users with uids, names, or mail addresses matching the strings between commas.', ), 'arguments' => array( 'users' => 'A comma delimited list of uids, user names, or email addresses.', ), 'required-arguments' => TRUE, 'options' => array( 'full' => 'show extended information about the user', 'short' => 'show basic information about the user (this is the default)', ), ); $items['user-block'] = array( 'callback' => 'drush_user_block', 'description' => 'Block the specified user(s).', 'aliases' => array('ublk'), 'arguments' => array( 'users' => 'A comma delimited list of uids, user names, or email addresses.', ), 'required-arguments' => TRUE, 'examples' => array( 'drush user-block 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => 'Block the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', ), 'options' => array( 'uid' => 'A comma delimited list of uids to block', 'name' => 'A comma delimited list of user names to block', 'mail' => 'A comma delimited list of user mail addresses to block', ), ); $items['user-unblock'] = array( 'callback' => 'drush_user_unblock', 'description' => 'Unblock the specified user(s).', 'aliases' => array('uublk'), 'arguments' => array( 'users' => 'A comma delimited list of uids, user names, or email addresses.', ), 'required-arguments' => TRUE, 'examples' => array( 'drush user-unblock 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => 'Unblock the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', ), 'options' => array( 'uid' => 'A comma delimited list of uids to unblock', 'name' => 'A comma delimited list of user names to unblock', 'mail' => 'A comma delimited list of user mail addresses to unblock', ), ); $items['user-add-role'] = array( 'callback' => 'drush_user_add_role', 'description' => 'Add a role to the specified user accounts.', 'aliases' => array('urol'), 'arguments' => array( 'role' => 'The name of the role to add', 'users' => '(optional) A comma delimited list of uids, user names, or email addresses.', ), 'required-arguments' => 1, 'examples' => array( 'drush user-add-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => 'Add the "power user" role to the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', ), 'options' => array( 'uid' => 'A comma delimited list of uids', 'name' => 'A comma delimited list of user names', 'mail' => 'A comma delimited list of user mail addresses', ), ); $items['user-remove-role'] = array( 'callback' => 'drush_user_remove_role', 'description' => 'Remove a role from the specified user accounts.', 'aliases' => array('urrol'), 'arguments' => array( 'role' => 'The name of the role to remove', 'users' => '(optional) A comma delimited list of uids, user names, or email addresses.', ), 'required-arguments' => 1, 'examples' => array( 'drush user-remove-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' => 'Remove the "power user" role from the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com', ), 'options' => array( 'uid' => 'A comma delimited list of uids', 'name' => 'A comma delimited list of user names', 'mail' => 'A comma delimited list of user mail addresses', ), ); $items['user-create'] = array( 'callback' => 'drush_user_create', 'description' => 'Create a user account with the specified name.', 'aliases' => array('ucrt'), 'arguments' => array( 'name' => 'The name of the account to add' ), 'required-arguments' => TRUE, 'examples' => array( 'drush user-create newuser --mail="person@example.com" --password="letmein"' => 'Create a new user account with the name newuser, the email address person@example.com, and the password letmein', ), 'options' => array( 'password' => 'The password for the new account', 'mail' => 'The email address for the new account', ), ); $items['user-cancel'] = array( 'callback' => 'drush_user_cancel', 'description' => 'Cancel a user account with the specified name.', 'aliases' => array('ucan'), 'arguments' => array( 'name' => 'The name of the account to cancel', ), 'required-arguments' => TRUE, 'examples' => array( 'drush user-cancel username' => 'Cancel the user account with the name username and anonymize all content created by that user.', ), ); $items['user-password'] = array( 'callback' => 'drush_user_password', 'description' => '(Re)Set the password for the user account with the specified name.', 'aliases' => array('upwd'), 'arguments' => array( 'name' => 'The name of the account to modify.' ), 'required-arguments' => TRUE, 'options' => array( 'password' => array( 'description' => 'The new password for the account.', 'required' => TRUE, 'example-value' => 'foo', ), ), 'examples' => array( 'drush user-password someuser --password="correct horse battery staple"' => 'Set the password for the username someuser. @see xkcd.com/936', ), ); $items['user-login'] = array( 'callback' => 'drush_user_login', 'description' => 'Display a one time login link for the given user account (defaults to uid 1).', 'aliases' => array('uli'), 'arguments' => array( 'user' => 'An optional uid, user name, or email address for the user to log in as. Default is to log in as uid 1. The uid/name/mail options take priority if specified.', 'path' => 'Optional path to redirect to after logging in.', ), 'options' => array( 'browser' => 'Optional value denotes which browser to use (defaults to operating system default). Set to 0 to suppress opening a browser.', 'uid' => 'A uid to log in as.', 'name' => 'A user name to log in as.', 'mail' => 'A user mail address to log in as.', ), 'examples' => array( 'drush user-login ryan node/add/blog' => 'Displays and opens default web browser (if configured or detected) for a one-time login link for the user with the username ryan and redirect to the path node/add/blog.', 'drush user-login --browser=firefox --mail=drush@example.org admin/settings/performance' => 'Open firefox web browser, login as the user with the e-mail address drush@example.org and redirect to the path admin/settings/performance.', ), ); return $items; } /** * Implements hook_drush_help_alter(). */ function user_drush_help_alter(&$command) { // Drupal 7+ only options. if ($command['command'] == 'user-cancel' && drush_drupal_major_version() >= 7) { $command['options']['delete-content'] = 'Delete all content created by the user'; $command['examples']['drush user-cancel --delete-content username'] = 'Cancel the user account with the name username and delete all content created by that user.'; } } /** * Prints information about the specified user(s). */ function drush_user_information($users) { $uids = _drush_user_get_users_from_arguments($users); foreach($uids as $uid) { _drush_user_print_info($uid); } } /** * Block the specified user(s). */ function drush_user_block($users = '') { $uids = _drush_user_get_users_from_options_and_arguments($users); if (!empty($uids)) { drush_op('user_user_operations_block', $uids); } else { return drush_set_error("Could not find any valid uids!"); } } /** * Unblock the specified user(s). */ function drush_user_unblock($users = '') { $uids = _drush_user_get_users_from_options_and_arguments($users); if (!empty($uids)) { drush_op('user_user_operations_unblock', $uids); } else { return drush_set_error("Could not find any valid uids!"); } } /** * Add a role to the specified user accounts. */ function drush_user_add_role($role, $users = '') { $uids = _drush_user_get_users_from_options_and_arguments($users); if (drush_drupal_major_version() >= 7) { $rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role)); } else { $rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role); } if (!empty($uids)) { if ($rid = drush_db_result($rid_query)) { drush_op('user_multiple_role_edit', $uids, 'add_role', $rid); foreach($uids as $uid) { drush_log(dt("Added the !role role to uid !uid", array('!role' => $role, '!uid' => $uid)), 'success'); } } else { return drush_set_error(dt("There is no role named: !role", array('!role' => $role))); } } else { return drush_set_error("Could not find any valid uids!"); } } /** * Remove a role from the specified user accounts. */ function drush_user_remove_role($role, $users = '') { $uids = _drush_user_get_users_from_options_and_arguments($users); if (drush_drupal_major_version() >= 7) { $rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role)); } else { $rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role); } if (!empty($uids)) { if ($rid = drush_db_result($rid_query)) { drush_op('user_multiple_role_edit', $uids, 'remove_role', $rid); foreach($uids as $uid) { drush_log(dt("Removed the !role role from uid !uid", array('!role' => $role, '!uid' => $uid)), 'success'); } } else { return drush_set_error(dt("There is no role named: !role", array('!role' => $role))); } } else { return drush_set_error("Could not find any valid uids!"); } } /** * Creates a new user account. */ function drush_user_create($name) { $mail = drush_get_option('mail'); $pass = drush_get_option('password'); $new_user = array( 'name' => $name, 'pass' => $pass, 'mail' => $mail, 'access' => '0', 'status' => 1, ); if (drush_drupal_major_version() >= 7) { $result = db_query("SELECT uid FROM {users} WHERE name = :name OR mail = :mail", array(':name' => $name, ':mail' => $new_user['mail'])); } else { $result = db_query("SELECT uid FROM {users} WHERE name = '%s' OR mail = '%s'", $name, $new_user['mail']); } if (drush_db_result($result) === FALSE) { if (!drush_get_context('DRUSH_SIMULATE')) { if (drush_drupal_major_version() >= 8) { $new_user_object = entity_create('user', $new_user); $new_user_object->save(); } else { $new_user_object = user_save(NULL, $new_user, NULL); } if ($new_user_object !== FALSE) { _drush_user_print_info($new_user_object->uid); return $new_user_object->uid; } else { drush_set_error("Could not create a new user account with the name " . $name . "!"); } } } else { drush_set_error("There is already a user account with the name " . $name . " or email address " . $new_user['mail'] . "!"); } } /** * Cancels a user account. */ function drush_user_cancel($name) { if (drush_drupal_major_version() >= 7) { $result = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $name)); } else { $result = db_query("SELECT uid FROM {users} WHERE name = '%s'", $name); } $uid = drush_db_result($result); if ($uid !== FALSE) { drush_print("Cancelling the user account with the following information:"); _drush_user_print_info($uid); if (drush_get_option('delete-content') && drush_drupal_major_version() >= 7) { drush_print("All content created by this user will be deleted!"); } if (drush_confirm('Cancel user account?: ')) { if (drush_drupal_major_version() >= 7) { if (drush_get_option('delete-content')) { user_cancel(array(), $uid, 'user_cancel_delete'); } else { user_cancel(array(), $uid, 'user_cancel_reassign'); } // I got the following technique here: http://drupal.org/node/638712 $batch =& batch_get(); $batch['progressive'] = FALSE; batch_process(); } else { user_delete(array(), $uid); } } } else { drush_set_error("Could not find a user account with the name " . $name . "!"); } } /** * Sets the password for the account with the given username */ function drush_user_password($name) { if (drush_drupal_major_version() >= 7) { $user = user_load_by_name($name); } else { $user = user_load(array('name' => $name)); } if ($user !== FALSE) { if (!drush_get_context('DRUSH_SIMULATE')) { $pass = drush_get_option('password'); // If no password has been provided, prompt for one. if (empty($pass)) $pass = drush_prompt(dt('Password'), NULL, TRUE, TRUE); if (drush_drupal_major_version() >= 8) { $user->pass = $pass; $user_object = $user->save(); } else { $user_object = user_save($user, array('pass' => $pass)); } if ($user_object === FALSE) { drush_set_error("Could not change the password for the user account with the name " . $name . "!"); } } } else { drush_set_error("The user account with the name " . $name . " could not be loaded!"); } } /** * Displays a one time login link for the given user. */ function drush_user_login($user = NULL, $path = NULL) { $user_object = $uid = FALSE; $args = func_get_args(); if (drush_get_option('uid', FALSE) || drush_get_option('name', FALSE) || drush_get_option('mail', FALSE)) { // One of the user options was passed, so we prefer that to the user // argument. $user = NULL; // If we only have a single argument and one of the user options is passed, // then we assume the argument is the path to open. if (count($args) == 1) { $path = $args[0]; } } else if (empty($user)) { // No user option or argument was passed, so we default to uid 1. $uid = 1; } // Try and load a user from provided options and arguments. if ($uid || $uid = reset(_drush_user_get_users_from_options_and_arguments($user))) { $user_object = user_load($uid); } if ($user_object !== FALSE && $user_object->status) { $options = array(); if ($path) { $options['query']['destination'] = $path; } $link = url(user_pass_reset_url($user_object) . '/login', $options); drush_start_browser($link); drush_print($link); return $link; } else { drush_set_error("The user account could not be loaded or is blocked!"); } } /** * Print information about a given uid */ function _drush_user_print_info($uid) { if (drush_drupal_major_version() >= 7) { $userinfo = user_load($uid); } else { $userinfo = user_load(array('uid' => $uid)); } if (drush_get_option('full')) { $userinfo = (array)$userinfo; $userinfo_pipe = array(); unset($userinfo['data']); unset($userinfo['block']); unset($userinfo['form_build_id']); foreach($userinfo as $key => $val) { if (is_array($val)) { drush_print($key . ': '); drush_print_r($val); $userinfo_pipe[] = '"' . implode(",", $val) . '"'; } else { if ($key === 'created' OR $key === 'access' OR $key === 'login') { drush_print($key . ': ' . format_date($val)); $userinfo_pipe[] = $val; } else { drush_print($key . ': ' . $val); $userinfo_pipe[] = $val; } } } drush_print_pipe(implode(",", $userinfo_pipe)); drush_print_pipe("\n"); } else { $userinfo_short = array( 'User ID' => $userinfo->uid, 'User name' => $userinfo->name, 'User mail' => $userinfo->mail, ); $userinfo_short['User roles'] = implode(', ', $userinfo->roles); $userinfo->status ? $userinfo_short['User status'] = 'active' : $userinfo_short['User status'] = 'blocked'; drush_print_table(drush_key_value_to_array_table($userinfo_short)); drush_print_pipe("$userinfo->name,$userinfo->uid,$userinfo->mail,$userinfo->status,\"" . implode(',', $userinfo->roles) . "\"\n"); } } /** * Given a comma-separated list of users, return uids * for users that match either by uid or email address. */ function _drush_user_get_users_from_arguments($users) { $uids = array(); if ($users !== '') { $users = explode(',', $users); foreach($users as $user) { $uid = _drush_user_get_uid($user); if ($uid !== FALSE) { $uids[] = $uid; } } } return $uids; } /** * Return the list of matching uids given */ function _drush_user_get_users_from_options_and_arguments($users) { $uids = drush_get_option_list('uids'); foreach (array('uid', 'name', 'mail' ) as $user_attr) { if ($arg = drush_get_option($user_attr)) { foreach(explode(',', $arg) as $search) { $uid_query = FALSE; switch ($user_attr) { case 'uid': if (drush_drupal_major_version() >= 7) { $uid_query = db_query("SELECT uid FROM {users} WHERE uid = :uid", array(':uid' => $search)); } else { $uid_query = db_query("SELECT uid FROM {users} WHERE uid = %d", $search); } break; case 'name': if (drush_drupal_major_version() >= 7) { $uid_query = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $search)); } else { $uid_query = db_query("SELECT uid FROM {users} WHERE name = '%s'", $search); } break; case 'mail': if (drush_drupal_major_version() >= 7) { $uid_query = db_query("SELECT uid FROM {users} WHERE mail = :mail", array(':mail' => $search)); } else { $uid_query = db_query("SELECT uid FROM {users} WHERE mail = '%s'", $search); } break; } if ($uid_query !== FALSE) { if ($uid = drush_db_result($uid_query)) { $uids[] = $uid; } else { drush_set_error("Could not find a uid for $user_attr = $search"); } } } } } return array_merge($uids, _drush_user_get_users_from_arguments($users)); } /** * Get uid(s) from a uid, user name, or email address. * Returns a uid, or FALSE if none found. */ function _drush_user_get_uid($search) { // We use a DB query while looking for the uid to keep things speedy. $uids = array(); if (is_numeric($search)) { if (drush_drupal_major_version() >= 7) { $uid_query = db_query("SELECT uid, name FROM {users} WHERE uid = :uid OR name = :name", array(':uid' => $search, ':name' => $search)); } else { $uid_query = db_query("SELECT uid, name FROM {users} WHERE uid = %d OR name = '%d'", $search, $search); } } else { if (drush_drupal_major_version() >= 7) { $uid_query = db_query("SELECT uid, name FROM {users} WHERE mail = :mail OR name = :name", array(':mail' => $search, ':name' => $search)); } else { $uid_query = db_query("SELECT uid, name FROM {users} WHERE mail = '%s' OR name = '%s'", $search, $search); } } while ($uid = drush_db_fetch_object($uid_query)) { $uids[$uid->uid] = $uid->name; } switch (count($uids)) { case 0: return drush_set_error("Could not find a uid for the search term '" . $search . "'!"); break; case 1: return array_pop(array_keys($uids)); break; default: drush_print('More than one user account was found for the search string "' . $search . '".'); return(drush_choice($uids, 'Please choose a name:', '!value (uid=!key)')); } } drush-5.10.0/docs/000077500000000000000000000000001222105546100136505ustar00rootroot00000000000000drush-5.10.0/docs/bastion.html000066400000000000000000000100211222105546100161670ustar00rootroot00000000000000

Remote Operations on Drupal Sites via a Bastion Server

Wikipedia defines a bastion server as "a special purpose computer on a network specifically designed and configured to withstand attacks." For the purposes of this documentation, though, any server that you can ssh through to reach other servers will do. Using standard ssh and drush techniques, it is possible to make a two-hop remote command look and act as if the destination machine is on the same network as the source machine.

Recap of Remote Site Aliases

Site aliases can refer to Drupal sites that are running on remote machines simply including 'remote-host' and 'remote-user' attributes:

$aliases['internal'] = array(
    'remote-host' => 'internal.company.com',
    'remote-user' => 'wwwadmin',
    'uri' => 'http://internal.company.com',
    'root' => '/path/to/remote/drupal/root',
);
With this alias defintion, you may use commands such as `drush @internal status`, 'drush ssh @internal` and `drush rsync @internal @dev` to operate remotely on the internal machine. What if you cannot reach the server that site is on from your current network? Enter the bastion server.

Setting up a Bastion server in .ssh/config

If you have access to a server, bastion.company.com, which you can ssh to from the open internet, and if the bastion server can in turn reach the internal server, then it is possible to configure ssh to route all traffic to the internal server through the bastion. The .ssh configuratin file would look something like this:

In .ssh/config:

Host internal.company.com
    ProxyCommand ssh user@bastion.company.com nc %h %p
That is all that is necessary; however, if the dev machine you are configuring is a laptop that might sometimes be inside the company intranet, you might want to optimize this setup so that the bastion is not used when the internal server can be reached directly. You could do this by changing the contents of your .ssh/config file when your network settings change -- or you could use drush.

Setting up a Bastion server via drush configuration

First, make sure that you do not have any configuration options for the internal machine in your .ssh/config file. The next step after that is to identify when you are connected to your company intranet. I like to determine this by using the `route` command to find my network gateway address, since this is always the same on my intranet, and unlikely to be encountered in other places.

In drushrc.php:

# Figure out if we are inside our company intranet by testing our gateway address against a known value
exec("route -n | grep '^0\.0\.0\.0' | awk '{ print $2; }' 2> /dev/null", $output);
if ($output[0] == '172.30.10.1') {
  drush_set_context('MY_INTRANET', TRUE);
}
After this code runs, the 'MY_INTRANET' context will be set if our gateway IP address matches the expected value, and unset otherwise. We can make use of this in our alias files.

In aliases.drushrc.php:

if (drush_get_context('MY_INTRANET', FALSE) === FALSE) {
  $aliases['intranet-proxy'] = array(
    'ssh-options' => ' -o "ProxyCommand ssh user@bastion.company.com nc %h %p"',
  );
}
$aliases['internal-server'] = array(
  'parent' => '@intranet-proxy',
  'remote-host' => 'internal.company.com',
  'remote-user' => 'wwwadmin',
);
$aliases['internal'] = array(
  'parent' => '@internal-server',
  'uri' => 'http://internal.company.com',
  'root' => '/path/to/remote/drupal/root',
);
The 'parent' term of the internal-server alias record is ignored if the alias it references ('@intranet-proxy') is not defined; the result is that 'ssh-options' will only be defined when outside of the intranet, and the ssh ProxyCommand to the bastion server will only be included when it is needed.

With this setup, you will be able to use your site alias '@internal' to remotely operate on your internal intranet Drupal site seemlessly, regardless of your location -- a handy trick indeed. drush-5.10.0/docs/bootstrap.html000066400000000000000000000106441222105546100165600ustar00rootroot00000000000000

The Drush Bootstrap Process

When preparing to run a command, drush works by "bootstrapping" the Drupal environment in very much the same way that is done during a normal page request from the web server, so most drush commands run in the context of a fully-initialized website.

For efficiency and convenience, some drush commands can work without first bootstrapping a Drupal site, or by only partially bootstrapping a site. This is more efficient, because there is sometimes a slight delay involved with bootstrapping, especially in some of the later stages. It is also a matter of convenience, because some commands are useful to use even when you do not have a working Drupal site available to bootstrap. For example, you can use drush to download Drupal with `drush dl drupal`. This obviously does not require any bootstrapping to work.

Starting with Drush-5, the loading of configuration files is no longer closely tied to the drush bootstrapping process. All configuration files are now loaded upfront, during DRUSH_BOOTSTRAP_DRUSH, with the Drupal root and site configuration files being loaded in advance of the corresponding bootstrap phase. See `drush topic docs-configuration` for details on drush configuration files.

DRUSH_BOOTSTRAP_DRUSH

Only bootstrap Drush, without any Drupal specific code.

Any code that operates on the Drush installation, and not specifically any Drupal directory, should bootstrap to this phase.

DRUSH_BOOTSTRAP_DRUPAL_ROOT

Set up and test for a valid drupal root, either through the -r/--root options, or evaluated based on the current working directory.

Any code that interacts with an entire Drupal installation, and not a specific site on the Drupal installation should use this bootstrap phase.

DRUSH_BOOTSTRAP_DRUPAL_SITE

Set up a Drupal site directory and the correct environment variables to allow Drupal to find the configuration file.

If no site is specified with the -l / --uri options, Drush will assume the site is 'default', which mimics Drupal's behaviour.

If you want to avoid this behaviour, it is recommended that you use the DRUSH_BOOTSTRAP_DRUPAL_ROOT bootstrap phase instead.

Any code that needs to modify or interact with a specific Drupal site's settings.php file should bootstrap to this phase.

DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION

Load the settings from the Drupal sites directory.

This phase is analagous to the DRUPAL_BOOTSTRAP_CONFIGURATION bootstrap phase in Drupal itself, and this is also the first step where Drupal specific code is included.

This phase is commonly used for code that interacts with the Drupal install API, as both install.php and update.php start at this phase.

DRUSH_BOOTSTRAP_DRUPAL_DATABASE

Connect to the Drupal database using the database credentials loaded during the previous bootstrap phase.

This phase is analogous to the DRUPAL_BOOTSTRAP_DATABASE bootstrap phase in Drupal.

Any code that needs to interact with the Drupal database API needs to be bootstrapped to at least this phase.

DRUSH_BOOTSTRAP_DRUPAL_FULL

Fully initialize Drupal.

This is analogous to the DRUPAL_BOOTSTRAP_FULL bootstrap phase in Drupal.

Any code that interacts with the general Drupal API should be bootstrapped to this phase.

DRUSH_BOOTSTRAP_DRUPAL_LOGIN

Log in to the initialiased Drupal site.

This bootstrap phase is used after the site has been fully bootstrapped. This is the default bootstrap phase all commands will try to reach, unless otherwise specified.

This phase will log you in to the drupal site with the username or user ID specified by the --user/ -u option.

Use this bootstrap phase for your command if you need to have access to information for a specific user, such as listing nodes that might be different based on who is logged in.

DRUSH_BOOTSTRAP_MAX

This is not an actual bootstrap phase. Commands that use DRUSH_BOOTSTRAP_MAX will cause drush to bootstrap as far as possible, and then run the command regardless of the bootstrap phase that was reached. This is useful for drush commands that work without a bootstrapped site, but that provide additional information or capabilities in the presence of a bootstrapped site. For example, `drush pm-releases modulename` works without a bootstrapped Drupal site, but will include the version number for the installed module if a Drupal site has been bootstrapped. drush-5.10.0/docs/commands.html000066400000000000000000000246441222105546100163510ustar00rootroot00000000000000

Creating Custom Drush Commands

Creating a new drush command is very easy. There are four simple steps.

  1. Create a command file called COMMANDFILE.drush.inc
  2. Implement the function COMMANDFILE_drush_command()
  3. Implement the functions that your commands will call. These will usually be named drush_COMMANDFILE_COMMANDNAME().

For an example drush command, see examples/sandwich.drush.inc. The steps for implementing your command are explained in more detail below.

Create COMMANDFILE.drush.inc

The name of your drush command is very important. It must end in ".drush.inc" to be recognized as a drush command. The part of the filename that comes before the ".drush.inc" becomes the name of the commandfile. Your commandfile name is used by drush to compose the names of the functions it will call, so choose wisely.

The example drush command, 'make-me-a-sandwich', is stored in the 'sandwich' commandfile, 'sandwich.drush.inc'. You can find this file in the 'examples' directory in the drush distribution.

Drush searches for commandfiles in the following locations:

  • The "/path/to/drush/commands" folder.
  • Folders listed in the 'include' option (see `drush topic docs-configuration`).
  • The system-wide drush commands folder, e.g. /usr/share/drush/commands
  • The ".drush" folder in the user's HOME folder.
  • sites/all/drush in the current Drupal installation
  • All enabled modules in the current Drupal installation
  • Folders and files containing other versions of drush in their names will be *skipped* (e.g. devel.drush4.inc or drush4/devel.drush.inc). Names containing the current version of drush (e.g. devel.drush5.inc) will be loaded.

Note that modules in the current Drupal installation will only be considered if drush has bootstrapped to at least the DRUSH_BOOSTRAP_SITE level. Usually, when working with a Drupal site, drush will bootstrap to DRUSH_BOOTSTRAP_FULL; in this case, only the drush commandfiles in enabled modules will be considered eligible for loading. If drush only bootstraps to DRUSH_BOOTSTRAP_SITE, though, then all drush commandfiles will be considered, whether the module is enabled or not. See `drush topic docs-bootstrap` for more information on bootstrapping.

Additionally, drush commandfiles may optionally define a function COMMANDFILE_drush_load() in the file COMMANDFILE.drush.load.inc. If this function returns FALSE, then the commandfile will not be loaded.

Implement COMMANDFILE_drush_command()

The drush_command hook is the most important part of the commandfile. It returns an array of items that define how your commands should be called, and how they work. Drush commands are very similar to the Drupal menu system. The elements that can appear in a drush command definition are shown below.

  • 'aliases': Provides a list of shorter names for the command. For example, pm-download may also be called via `drush dl`. If the alias is used, drush will substitute back in the primary command name, so pm-download will still be used to generate the command hook, etc.
  • 'command hook': Change the name of the function drush will call to execute the command from drush_COMMANDFILE_COMMANDNAME() to drush_COMMANDFILE_COMMANDHOOK(), where COMMANDNAME is the original name of the command, and COMMANDHOOK is the value of the 'command hook' item.
  • 'callback': Name of function to invoke for this command. The callback function name _must_ begin with "drush_commandfile_", where commandfile is from the file "commandfile.drush.inc", which contains the commandfile_drush_command() function that returned this command. Note that the callback entry is optional; it is preferable to omit it, in which case drush_invoke() will generate the hook function name.
  • 'callback arguments': An array of arguments to pass to the callback. The command line arguments, if any, will appear after the callback arguments in the function parameters.
  • 'description': Description of the command.
  • 'arguments': An array of arguments that are understood by the command. Used by `drush help` only.
  • 'required-arguments': Defaults to FALSE; TRUE if all of the arguments are required. Set to an integer count of required arguments if only some are required.
  • 'options': An array of options that are understood by the command. Any option that the command expects to be able to query via drush_get_option _must_ be listed in the options array. If it is not, users will get an error about an "Unknown option" when they try to specify the option on the command line.

    The value of each option may be either a simple string containing the option description, or an array containing the following information:

    • 'description': A description of the option.
    • 'example_value': An example value to show in help.
    • 'value': optional|required.
    • 'required': This option must be passed.
    • 'hidden': The option is not shown in the help output (rare).
  • 'allow-additional-options': If TRUE, then the ordinary testing to see if options exist will be skipped. Examples of where this is done includes the core-rsync command, which passes options along to the rsync shell command.

    This item may also contain a list of other commands that are invoked as subcommands (e.g. the pm-update command calls pm-updatecode and updatedb commands). When this is done, the options from the subcommand may be used on the commandline, and are also listed in the command's `help` output.

    Defaults to FALSE.

  • 'examples': An array of examples that are understood by the command. Used by `drush help` only.
  • 'scope': One of 'system', 'project', 'site'. Not currently used.
  • 'bootstrap': Drupal bootstrap level. Valid values are:
    • DRUSH_BOOTSTRAP_DRUSH
    • DRUSH_BOOTSTRAP_DRUPAL_ROOT
    • DRUSH_BOOTSTRAP_DRUPAL_SITE
    • DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION
    • DRUSH_BOOTSTRAP_DRUPAL_DATABASE
    • DRUSH_BOOTSTRAP_DRUPAL_FULL
    • DRUSH_BOOTSTRAP_DRUPAL_LOGIN
    • DRUSH_BOOTSTRAP_MAX
    The default value is DRUSH_BOOTSTRAP_DRUPAL_LOGIN. See `drush topic docs-bootstrap`.
  • 'core': Drupal major version required. Append a '+' to indicate 'and later versions.'
  • 'drupal dependencies': Drupal modules required for this command.
  • 'drush dependencies': Other drush commandfiles required for this command.
  • 'topics': Provides a list of topic commands that are related in some way to this command. Used by `drush help`.
  • 'topic': Set to TRUE if this command is a topic, callable from the `drush docs-topics` command.

The 'sandwich' drush_command hook looks like this:

        function sandwich_drush_command() {
          $items = array();

          $items['make-me-a-sandwich'] = array(
            'description' => "Makes a delicious sandwich.",
            'arguments' => array(
              'filling' => 'The type of the sandwich (turkey, cheese, etc.)',
            ),
            'options' => array(
              'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)',
            ),
            'examples' => array(
              'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
            ),
            'aliases' => array('mmas'),
            'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
          );

          return $items;
        }

Most of the items in the 'make-me-a-sandwich' command definition have no effect on execution, and are used only by `drush help`. The exceptions are 'aliases' (described above) and 'bootstrap'. As previously mentioned, `drush topic docs-bootstrap` explains the drush bootstrapping process in detail.

Implement drush_COMMANDFILE_COMMANDNAME()

The 'make-me-a-sandwich' command in sandwich.drush.inc is defined as follows:

	function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
	  ... implementation here ...
        }

If a user runs `drush make-me-a-sandwich` with no command line arguments, then drush will call drush_sandwich_make_me_a_sandwich() with no function parameters; in this case, $filling will take on the provided default value, 'ascii'. (If there is no default value provided, then the variable will be NULL, and a warning will be printed.) Running `drush make-me-a-sandwich ham` will cause drush to call drush_sandwich_make_me_a_sandwich('ham'). In the same way, commands that take two command line arguments can simply define two functional parameters, and a command that takes a variable number of command line arguments can use the standard php function func_get_args() to get them all in an array for easy processing.

It is also very easy to query the command options using the function drush_get_option(). For example, in the drush_sandwich_make_me_a_sandwich() function, the --spreads option is retrieved as follows:

        $str_spreads = '';
        if ($spreads = drush_get_option('spreads')) {
          $list = implode(' and ', explode(',', $spreads));
          $str_spreads = ' with just a dash of ' . $list;
        }

Note that drush will actually call a sequence of functions before and after your drush command function. One of these hooks is the "validate" hook. The 'sandwich' commandfile provides a validate hook for the 'make-me-a-sandwich' command:

        function drush_sandwich_make_me_a_sandwich_validate() {
          $name = posix_getpwuid(posix_geteuid());
          if ($name['name'] !== 'root') {
            return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
          }
        }

The validate function should call drush_set_error and return its result if the command cannot be validated for some reason. See `drush topic docs-policy` for more information on defining policy functions with validate hooks, and `drush topic docs-api` for information on how the command hook process works. Also, the list of defined drush error codes can be found in `drush topic docs-errorcodes`.

To see the full implementation of the sample 'make-me-a-sandwich' command, see `drush topic docs-examplecommand`. drush-5.10.0/docs/context.html000066400000000000000000000057531222105546100162340ustar00rootroot00000000000000

Drush Contexts

The drush contexts API acts as a storage mechanism for all options, arguments and configuration settings that are loaded into drush.

This API also acts as an IPC mechanism between the different drush commands, and provides protection from accidentally overriding settings that are needed by other parts of the system.

It also avoids the necessity to pass references through the command chain and allows the scripts to keep track of whether any settings have changed since the previous execution.

This API defines several contexts that are used by default.

Argument contexts

These contexts are used by Drush to store information on the command. They have their own access functions in the forms of drush_set_arguments(), drush_get_arguments(), drush_set_command(), drush_get_command().

  • command : The drush command being executed.
  • arguments : Any additional arguments that were specified.

Setting contexts

These contexts store options that have been passed to the drush.php script, either through the use of any of the config files, directly from the command line through --option='value' or through a JSON encoded string passed through the STDIN pipe.

These contexts are accessible through the drush_get_option() and drush_set_option() functions. See drush_context_names() for a description of all of the contexts.

Drush commands may also choose to save settings for a specific context to the matching configuration file through the drush_save_config() function.

Available Setting contexts

These contexts are evaluated in a certain order, and the highest priority value is returned by default from drush_get_option. This allows scripts to check whether an option was different before the current execution.

Specified by the script itself :

  • process : Generated in the current process.
  • cli : Passed as --option=value to the command line.
  • stdin : Passed as a JSON encoded string through stdin.
  • alias : Defined in an alias record, and set in the alias context whenever that alias is used.
  • specific : Defined in a command-specific option record, and set in the command context whenever that command is used.

Specified by config files :

  • custom : Loaded from the config file specified by --config or -c
  • site : Loaded from the drushrc.php file in the Drupal site directory.
  • drupal : Loaded from the drushrc.php file in the Drupal root directory.
  • user : Loaded from the drushrc.php file in the user's home directory.
  • drush : Loaded from the drushrc.php file in the $HOME/.drush directory.
  • system : Loaded from the drushrc.php file in the system's $PREFIX/etc/drush directory.
  • drush : Loaded from the drushrc.php file in the same directory as drush.php.

Specified by the script, but has the lowest priority :

  • default : The script might provide some sensible defaults during init.
drush-5.10.0/docs/cron.html000066400000000000000000000070121222105546100154770ustar00rootroot00000000000000

Running Drupal cron tasks from drush

Drupal cron tasks are often set up to be run via a wget call to cron.php; this same task can also be accomplished via the `drush cron` command, which circumvents the need to provide a webserver interface to cron.

Quickstart

If you just want to get started quickly, here is a crontab entry that will run cron once every hour at ten minutes after the hour:

10 * * * * /usr/bin/env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin COLUMNS=72 /usr/local/drush/drush --root=/path/to/your/drupalroot --uri=your.drupalsite.org --quiet cron

You should set up crontab to run your cron tasks as the same user that runs the web server; for example, if you run your webserver as the user www-data:

sudo -u www-data crontab -e

You might need to edit the crontab entry shown above slightly for your paricular setup; for example, if you have installed drush to some directory other than /usr/local/drush, then you will need to adjust the path to drush appropriately. We'll break down the meaning of each section of the crontab entry in the documentation that continues below.

Setting the schedule

See `man 5 crontab` for information on how to format the information in a crontab entry. In the example above, the schedule for the crontab is set by the string "10 * * * *". These fields are the minute, hour, day of month, month and day of week; "*" means essentially "all values", so "10 * * * *" will run any time the minute == 10 (once every hour).

Setting the PATH

We use /usr/bin/env to run drush so that we can set up some necessary environment variables that drush needs to execute. By default, cron will run each command with an empty PATH, which would not work well with drush. To find out what your PATH needs to be, just type:

echo $PATH

Take the value that is output and place it into your crontab entry in the place of the one shown above. You can remove any entry that is known to not be of interest to drush (e.g. /usr/games), or is only useful in a graphic environment (e.g. /usr/X11/bin).

Setting COLUMNS

When running drush in a terminal, the number of columns will be automatically deteremined by drush by way of the tput command, which queries the active terminal to determine what the width of the screen is. When running drush from cron, there will not be any terminal set, and the call to tput will produce an error message. Spurrious error messages are undesirable, as cron is often configured to send email whenever any output is produced, so it is important to make an effort to insure that successful runs of cron complete with no output.

In some cases, drush is smart enough to recognize that there is no terminal -- if the terminal value is empty or "dumb", for example. However, there are some "non-terminal" values that drush does not recognize, such as "unknown." If you manually set COLUMNS, then drush will repect your setting and will not attempt to call tput.

Using --quiet

By default, drush will print a success message when the run of cron is completed. The --quiet flag will suppress these and other progress messages, again avoiding an unnecessary email message.

Specifying the Drupal site to run

There are many ways to tell drush which Drupal site to select for the active command, and any may be used here. The example uses the --root and --uri flags, but you could also use an alias record if you defined it in a global location, such as /etc/drush/aliases.drushrc.php. drush-5.10.0/docs/drush.api.php000066400000000000000000000252131222105546100162610ustar00rootroot00000000000000 0;"); } /** * Add help components to a command */ function hook_drush_help_alter(&$command) { if ($command['command'] == 'sql-sync') { $command['options']['myoption'] = "Description of modification of sql-sync done by hook"; $command['sub-options']['sanitize']['my-sanitize-option'] = "Description of sanitization option added by hook (grouped with --sanitize option)"; } if ($command['command'] == 'global-options') { // Recommended: don't show global hook options in brief global options help. if ($command['#brief'] === FALSE) { $command['options']['myglobaloption'] = 'Description of option used globally in all commands (e.g. in a commandfile init hook)'; } } } /** * Add/edit options to cache-clear command */ function hook_drush_cache_clear(&$types) { $types['views'] = 'views_invalidate_cache'; } /** * Inform drush about one or more engine types. * * This hook allow to declare available engine types, the cli option to select * between engine implementatins, which one to use by default, global options * and other parameters. Commands may override this info when declaring the * engines they use. * * @return * An array whose keys are engine type names and whose values describe * the characteristics of the engine type in relation to command definitions: * * - description: The engine type description. * - option: The command line option to choose an implementation for * this engine type. * FALSE means there's no option. That is, the engine type is for internal * usage of the command and thus an implementation is not selectable. * - default: The default implementation to use by the engine type. * - options: Engine options common to all implementations. * - add-options-to-command: If there's a single implementation for this * engine type, add its options as command level options. * * @see drush_get_engine_types_info() * @see pm_drush_engine_type_info() */ function hook_drush_engine_type_info() { return array( 'dessert' => array( 'description' => 'Choose a dessert while the sandwich is baked.', 'option' => 'dessert', 'default' => 'ice-cream', 'options' => 'sweetness', 'add-options-to-command' => FALSE, ), ); } /** * Inform drush about one or more engines implementing a given engine type. * * This hook allow to declare implementations for an engine type. * * @see pm_drush_engine_package_handler() * @see pm_drush_engine_version_control() */ function hook_drush_engine_ENGINE_TYPE() { return array( 'ice-cream' => array( 'description' => 'Feature rich ice-cream with all kind of additives.', 'options' => array( 'flavour' => 'Choose your favorite flavour', ), ), ); } /** * @} End of "addtogroup hooks". */ drush-5.10.0/docs/make.txt000066400000000000000000000324301222105546100153300ustar00rootroot00000000000000 Drush make ---------- Drush make is an extension to drush that can create a ready-to-use drupal site, pulling sources from various locations. It does this by parsing a flat text file (similar to a drupal `.info` file) and downloading the sources it describes. In practical terms, this means that it is possible to distribute a complicated Drupal distribution as a single text file. Among drush make's capabilities are: - Downloading Drupal core, as well as contrib modules from drupal.org. - Checking code out from SVN, git, and bzr repositories. - Getting plain `.tar.gz` and `.zip` files (particularly useful for libraries that can not be distributed directly with drupal core or modules). - Fetching and applying patches. - Fetching modules, themes, and installation profiles, but also external libraries. Usage ----- The `drush make` command can be executed from a path within a Drupal codebase or independent of any Drupal sites entirely. See the examples below for instances where `drush make` can be used within an existing Drupal site. drush make [-options] [filename.make] [build path] The `.make` file format ----------------------- Each makefile is a plain text file that adheres to the Drupal `.info` file syntax. See the included `example.make` for an example of a working makefile. ### Core version The make file always begins by specifying the core version of Drupal for which each package must be compatible. Example: core = 6.x ### API version The make file must specify which Drush Make API version it uses. This version of Drush Make uses API version `2` api = 2 ### Projects An array of the projects (e.g. modules, themes, libraries, and drupal) to be retrieved. Each project name can be specified as a single string value. If further options need to be provided for a project, the project should be specified as the key. **Project with no further options:** projects[] = drupal **Project using options (see below):** projects[drupal][version] = 7.12 Do not use both types of declarations for a single project in your makefile. ### Project options - `version` Specifies the version of the project to retrieve. This can be as loose as the major branch number or as specific as a particular point release. projects[views][version] = 3 projects[views][version] = 2.8 projects[views][version] = 3.0-alpha2 ; Shorthand syntax for versions if no other options are to be specified projects[views] = 3.0-alpha2 - `patch` One or more patches to apply to this project. An array of URLs from which each patch should be retrieved. projects[calendar][patch][rfc-fixes][url] = "http://drupal.org/files/issues/cal-760316-rfc-fixes-2.diff" projects[calendar][patch][rfc-fixes][md5] = "e4876228f449cb0c37ffa0f2142" ; shorthand syntax if no md5 checksum is specified projects[adminrole][patch][] = "http://drupal.org/files/issues/adminrole_exceptions.patch" - `subdir` Place a project within a subdirectory of the `--contrib-destination` specified. In the example below, `cck` will be placed in `sites/all/modules/contrib` instead of the default `sites/all/modules`. projects[cck][subdir] = "contrib" - `location` URL of an alternate project update XML server to use. Allows project XML data to be retrieved from sites other than `updates.drupal.org`. projects[tao][location] = "http://code.developmentseed.com/fserver" - `type` The project type. Must be provided if an update XML source is not specified and/or using version control or direct retrieval for a project. May be one of the following values: core, module, profile, theme. projects[mytheme][type] = "theme" - `directory_name` Provide an alternative directory name for this project. By default, the project name is used. projects[mytheme][directory_name] = "yourtheme" - `l10n_path` Specific URL (can include tokens) to a translation. Allows translations to be retrieved from l10n servers other than `localize.drupal.org`. projects[mytheme][l10n_path] = "http://myl10nserver.com/files/translations/%project-%core-%version-%language.po" - `l10n_url` URL to an l10n server XML info file. Allows translations to be retrieved from l10n servers other than `localize.drupal.org`. projects[mytheme][l10n_url] = "http://myl10nserver.com/l10n_server.xml" - `overwrite` Allows the project to be installed in a directory that is not empty. If not specified this is treated as FALSE, drush_make sets an error when the directory is not empty. If specified TRUE, drush_make will continue and use the existing directory. Useful when adding extra files and folders to existing folders in libraries or module extensions. projects[myproject][overwrite] = TRUE - `translations` Retrieve translations for the specified language, if available, for all projects. translations[] = es ### Project download options Use an alternative download method instead of retrieval through update XML. If no download type is specified, make defaults the type to `git`. Additionally, if no url is specified, make defaults to use Drupal.org. The following methods are available: - `download[type] = file` Retrieve a project as a direct download. Options: `url` - the URL of the file. Required. `md5`, `sha1`, `sha256`, or `sha512` - one or more checksums for the file. Optional. `request_type` - the request type - get or post. post depends on http://drupal.org/project/make_post. Optional. `data` - The post data to be submitted with the request. Should be a valid URL query string. Requires http://drupal.org/project/make_post. Optional. `filename` - What to name the file, if it's not an archive. Optional. `subtree` - if the download is an archive, only this subtree within the archive will be copied to the target destination. Optional. - `download[type] = bzr` Use a bazaar repository as the source for this project. Options: `url` - the URL of the repository. Required. - `download[type] = git` Use a git repository as the source for this project. Options: `url` - the URL of the repository. Required. `branch` - the branch to be checked out. Optional. `revision` - a specific revision identified by commit to check out. Optional. `tag` - the tag to be checked out. Optional. projects[mytheme][download][type] = "git" projects[mytheme][download][url] = "git://github.com/jane_doe/mytheme.git" Shorthand is available to pull a specific revision from a git repository: projects[context_admin][revision] = "eb9f05e" is the same as: projects[context_admin][download][revision] = "eb9f05e" `refspec` - the git reference to fetch and checkout. Optional. If this is set, it will have priority over tag, revision and branch options. - `download[type] = svn` Use an SVN repository as the source for this project. Options: `url` - the URL of the repository. Required. `interactive` - whether to prompt the user for authentication credentials when using a private repository. Allows username and/or password options to be omitted. Optional. `username` - the username to use when retrieving an SVN project as a working copy or from a private repository. Optional. `password` - the password to use when retrieving an SVN project as a working copy or from a private repository. Optional. projects[mytheme][download][type] = "svn" projects[mytheme][download][url] = "http://example.com/svnrepo/cool-theme/" ### Libraries An array of non-Drupal-specific libraries to be retrieved (e.g. js, PHP or other Drupal-agnostic components). Each library should be specified as the key of an array of options in the libraries array. **Example:** libraries[jquery_ui][download][type] = "file" libraries[jquery_ui][download][url] = "http://jquery- ui.googlecode.com/files/jquery.ui-1.6.zip" libraries[jquery_ui][download][md5] = "c177d38bc7af59d696b2efd7dda5c605" ### Library options Libraries share the `download`, `subdir`, and `directory_name` options with projects. Additionally, they may specify a destination: - `destination` The target path to which this library should be moved. The path is relative to that specified by the `--contrib-destination` option. By default, libraries are placed in the `libraries` directory. libraries[jquery_ui][destination] = "modules/contrib/jquery_ui ### Includes An array of makefiles to include. Each include may be a local relative path to the includer makefile directory or a direct URL to the makefile. Includes are appended in order with the source makefile appended last, allowing latter makefiles to override the keys/values of former makefiles. **Example:** includes[example] = "example.make" includes[example_relative] = "../example_relative/example_relative.make" includes[remote] = "http://www.example.com/remote.make" ### Defaults If all projects or libraries have identical settings for a given attribute, the `defaults` array can be used to specify these, rather than specifying the attribute for each project. **Example:** ; Specify common subdir of "contrib" defaults[projects][subdir] = "contrib" ; Projects that don't specify subdir will go to the 'contrib' directory. projects[views][version] = "3.3" ; Override default value projects[devel][subdir] = "development" ### Overriding properties Makefiles which include others may override the included makefiles properties. Properties in the includer takes precedence over the includee. **Example:** `base.make` core = "6.x" projects[views][subdir] = "contrib" projects[cck][subdir] = "contrib" `extender.make` includes[base] = "base.make" ; This line overrides the included makefile's 'subdir' option projects[views][subdir] = "patched" ; This line overrides the included makefile, switching the download type ; to a git clone projects[views][type] = "module" projects[views][download][type] = "git" projects[views][download][url] = "http://git.drupal.org/project/views.git" A project or library entry of an included makefile can be removed entirely by setting the corresponding key to FALSE: ; This line removes CCK entirely which was defined in base.make projects[cck] = FALSE Recursion --------- If a project that is part of a build contains a `.make` itself, drush make will automatically parse it and recurse into a derivative build. For example, a full build tree may look something like this: drush make distro.make distro distro.make FOUND - Drupal core - Foo bar install profile + foobar.make FOUND - CCK - Token - Module x + x.make FOUND - External library x.js - Views - etc. Recursion can be used to nest an install profile build in a Drupal site, easily build multiple install profiles on the same site, fetch library dependencies for a given module, or bundle a set of module and its dependencies together. For Drush Make to recognize a makefile embedded within a project, the makefile itself must have the same name as the project. For instance, the makefile embedded within the managingnews profile must be called "managingnews.make". The file should also be in the project's root directory. Subdirectories will be ignored. **Build a full Drupal site with the Managing News install profile:** core = 6.x projects[] = drupal projects[] = managingnews Testing ------- Drush make also comes with testing capabilities, designed to test drush make itself. Writing a new test is extremely simple. The process is as follows: 1. Figure out what you want to test. Write a makefile that will test this out. You can refer to existing test makefiles for examples. These are located in `DRUSH/tests/makefiles`. 2. Drush make your makefile, and use the --md5 option. You may also use other options, but be sure to take note of which ones for step 4. 3. Verify that the result you got was in fact what you expected. If so, continue. If not, tweak it and re-run step 2 until it's what you expected. 4. Using the md5 hash that was spit out from step 2, make a new entry in the tests clase (DRUSH/tests/makeTest.php), following the example below. 'machine-readable-name' => array( 'name' => 'Human readable name', 'makefile' => 'tests/yourtest.make', 'messages' => array( 'Build hash: f68e6510-your-hash-e04fbb4ed', ), 'options' => array('any' => TRUE, 'other' => TRUE, 'options' => TRUE), ), 5. Test! Run drush test suite (see DRUSH/tests/README.md). To just run the make tests: `phpunit --filter=makeMake .` You can check for any messages you want in the message array, but the most basic tests would just check the build hash. Generate -------- Drush make has a primitive makefile generation capability. To use it, simply change your directory to the Drupal installation from which you would like to generate the file, and run the following command: `drush generate-makefile /path/to/make-file.make` This will generate a basic makefile. If you have code from other repositories, the makefile will not complete - you'll have to fill in some information before it is fully functional. Maintainers ----------- - Jonathan Hedstrom (jhedstrom) - The rest of the Drush maintainers Original Author --------------- Dmitri Gaskin (dmitrig01) drush-5.10.0/docs/shellaliases.html000066400000000000000000000042521222105546100172120ustar00rootroot00000000000000

Drush Shell Aliases

A Drush shell alias is a shortcut to any Drush command or any shell command. Drush shell aliases are very similar to git aliases.

See: https://git.wiki.kernel.org/index.php/Aliases#Advanced

A shell alias is defined in a Drush configuration file called drushrc.php. See `drush topic docs-configuration`. There are two kinds of shell aliases: an alias whose value begins with a '!' will execute the rest of the line as bash commands. Aliases that do not start with a '!' will be interpreted as Drush commands.

Example:

    $options['shell-aliases']['pull'] = '!git pull';
    $options['shell-aliases']['noncore'] = 'pm-list --no-core';
With the above two aliases defined, `drush pull` will then be equivalent to `git pull`, and `drush noncore` will be equivalent to `drush pm-list --no-core`.

Shell Alias Replacements

Shell aliases are even more powerful when combined with shell alias replacements and site aliases. Shell alias replacements take the form of {{sitealias-item}} or {{%pathalias-item}}, and also the special {{@target}}, which is replaced with the name of the site alias used, or '@none' if none was used.

For example, given the following site alias:

     $aliases['dev'] = array (
       'root' => '/path/to/drupal',
       'uri' => 'mysite.org',
       '#live' => '@acme.live',
     );
The alias below can be used for all your projects to fetch the database and files from the client's live site via `drush @dev pull-data`. Note that these aliases assume that the alias used defines an item named '#live' (as shown in the above alias).

Shell aliases using replacements:

    $options['shell-aliases']['pull-data'] = '!drush sql-sync {{#live}} {{@target}} && drush rsync {{#live}}:%files {{@target}}:%files';
If the user does not use these shell aliases with any site alias, then an error will be returned and the script will not run. These aliases with replacements can be used to quickly run combinations of drush sql-sync and rsync commands on the "standard" source or target site, reducing the risk of typos that might send information in the wrong direction or to the wrong site. drush-5.10.0/docs/shellscripts.html000066400000000000000000000056111222105546100172600ustar00rootroot00000000000000

Drush Shell Scripts

A drush shell script is any Unix shell script file that has its "execute" bit set (i.e., via `chmod +x myscript.drush`) and that begins with a specific line:

	#!/usr/bin/env drush
- or -
 
	#!/full/path/to/drush

The former is the usual form, and is more convenient in that it will allow you to run the script regardless of where drush has been installed on your system, as long as it appears in your PATH. The later form allows you to specify the drush command add options to use, as in:

	#!/full/path/to/drush php-script --some-option

Adding specific options is important only in certain cases, described later; it is usually not necessary.

Drush scripts do not need to be named "*.drush" or "*.script"; they can be named anything at all. To run them, make sure they are executable (`chmod +x helloworld.script`) and then run them from the shell like any other script.

There are two big advantages to drush scripts over bash scripts:

  • They are written in php
  • drush can bootstrap your Drupal site before running your script.

To bootstrap a Drupal site, provide an alias to the site to bootstrap as the first commandline argument.

For example:

	$ helloworld.script @dev a b c

If the first argument is a valid site alias, drush will remove it from the arument list and bootstrap that site, then run your script. The script itself will not see @dev on its argument list. If you do not want drush to remove the first site alias from your scripts argument list (e.g. if your script wishes to syncronise two sites, specified by the first two arguments, and does not want to bootstrap either of those two sites), then fully specify the drush command (php-script) and options to use, as shown above. By default, if the drush command is not specified, drush will provide the following default line:

	#!/full/path/to/drush php-script --bootstrap-to-first-arg

It is the option --bootstrap-to-first-arg that causes drush to pull off the first argument and bootstrap it. The way to get rid of that option is to specify the php-script line to run, and leave it off, like so:

	#!/full/path/to/drush php-script

Note that 'php-script' is the only built-in drush command that makes sense to put on the "shebang" ("#!" is pronounced "shebang") line. However, if you wanted to, you could implement your own custom version of php-script (e.g. to preprocess the script input, perhaps), and specify that command on the shebang line.

Drush scripts can access their arguments via the drush_shift() function:

        while ($arg = drush_shift()) {
          drush_print($arg);
        }

Options are available via drush_get_option('option-name').

See the example drush script in `drush topic docs-examplescript`, and the list of drush error codes in `drush topic docs-errorcodes`. drush-5.10.0/docs/strict-options.html000066400000000000000000000031641222105546100175430ustar00rootroot00000000000000

Strict Option Handling

Some Drush commands use strict option handling; these commands require that all Drush global option appear on the command line before the Drush command name.

One example of this is the core-rsync command:

  drush --simulate core-rsync -v @site1 @site2
The --simulate option is a Drush global option that causes Drush to print out what it would do if the command is executed, without actually taking any action. Commands such as core-rsync that use strict option handling require that --simulate, if used, must appear before the command name. Most Drush commands allow the --simulate to be placed anywhere, such as at the end of the command line.

The -v option above is an rsync option. In this usage, it will cause the rsync command to run in verbose mode. It will not cause Drush to run in verbose mode, though, because it appears after the core-rsync command name. Most Drush commands would be run in verbose mode if a -v option appeared in the same location.

The advantage of strict option handling is that it allows Drush to pass options and arguments through to a shell command. Some shell commands, such as rsync and ssh, either have options that cannot be represented in Drush. For example, rsync allows the --exclude option to appear multiple times on the command line, but Drush only allows one instance of an option at a time for most Drush commands. Strict option handling overcomes this limitation, plus possible conflict between Drush options and shell command options with the same name, at the cost of greater restriction on where global options can be placed. drush-5.10.0/drush000077500000000000000000000112221222105546100137710ustar00rootroot00000000000000#!/usr/bin/env sh # $Id$ # # This script is a simple wrapper that will run Drush with the most appropriate # php executable it can find. # # Solaris users: Add /usr/xpg4/bin to the head of your PATH # # Get the absolute path of this executable SELF_DIRNAME="`dirname -- "$0"`" SELF_PATH="`cd -P -- "$SELF_DIRNAME" && pwd -P`/`basename -- "$0"`" # Decide if we are running a Unix shell on Windows if [ `which uname` ]; then case "`uname -a`" in CYGWIN*) CYGWIN=1 ;; MINGW*) MINGW=1 ;; esac fi # Resolve symlinks - this is the equivalent of "readlink -f", but also works with non-standard OS X readlink. while [ -h "$SELF_PATH" ]; do # 1) cd to directory of the symlink # 2) cd to the directory of where the symlink points # 3) Get the pwd # 4) Append the basename DIR="`dirname -- "$SELF_PATH"`" SYM="`readlink $SELF_PATH`" SYM_DIRNAME="`dirname -- "$SYM"`" SELF_PATH="`cd "$DIR" && cd "$SYM_DIRNAME" && pwd`/`basename -- "$SYM"`" done # Build the path to drush.php. SCRIPT_PATH="`dirname "$SELF_PATH"`/drush.php" if [ -n "$CYGWIN" ] ; then SCRIPT_PATH="`cygpath -w -a -- "$SCRIPT_PATH"`" fi # If not exported, try to determine and export the number of columns. # We do not want to run `tput cols` if $TERM is empty or "dumb", because # if we do, tput will output an undesirable error message to stderr. If # we redirect stderr in any way, e.g. `tput cols 2>/dev/null`, then the # error message is suppressed, but tput cols becomes confused about the # terminal and prints out the default value (80). if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] && [ -n "`which tput`" ] ; then # Note to cygwin/mingw/msys users: install the ncurses package to get tput command. # Note to mingw/msys users: there is no precompiled ncurses package. if COLUMNS="`tput cols`"; then export COLUMNS fi fi if [ -n "$DRUSH_PHP" ] ; then # Use the DRUSH_PHP environment variable if it is available. php="$DRUSH_PHP" else # On MSYSGIT, we need to use "php", not the full path to php if [ -n "$MINGW" ] ; then php="php" else # Default to using the php that we find on the PATH. # We check for a command line (cli) version of php, and if found use that. # Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 php="`which php-cli 2>/dev/null`" if [ ! -x "$php" ]; then php="`which php 2>/dev/null`" fi if [ ! -x "$php" ]; then echo "ERROR: can't find php."; exit 1 fi fi fi # Check to see if the user has provided a php.ini file or drush.ini file in any conf dir # Last found wins, so search in reverse priority order for conf_dir in "`dirname "$SELF_PATH"`" /etc/drush "$HOME/.drush" ; do if [ ! -d "$conf_dir" ] ; then continue fi # Handle paths that don't start with a drive letter on MinGW shell. Equivalent to cygpath on Cygwin. if [ -n "$MINGW" ] ; then conf_dir=`sh -c "cd "$conf_dir"; pwd -W"` fi if [ -f "$conf_dir/php.ini" ] ; then drush_php_ini="$conf_dir/php.ini" fi if [ -f "$conf_dir/drush.ini" ] ; then drush_php_override="$conf_dir/drush.ini" fi done # If the PHP_INI environment variable is specified, then tell # php to use the php.ini file that it specifies. if [ -n "$PHP_INI" ] ; then drush_php_ini="$PHP_INI" fi # If the DRUSH_INI environment variable is specified, then # extract all ini variable assignments from it and convert # them into php '-d' options. These will override similarly-named # options in the php.ini file if [ -n "$DRUSH_INI" ] ; then drush_php_override="$DRUSH_INI" fi # Add in the php file location and/or the php override variables as appropriate if [ -n "$drush_php_ini" ] ; then php_options="--php-ini $drush_php_ini" fi if [ -n "$drush_php_override" ] ; then php_options=`grep '^[a-z_A-Z0-9.]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' ' '` fi # If the PHP_OPTIONS environment variable is specified, then # its contents will be passed to php on the command line as # additional options to use. if [ -n "$PHP_OPTIONS" ] ; then php_options="$php_options $PHP_OPTIONS" fi # Always disable magic_quotes_gpc and friends php_options="$php_options -d magic_quotes_gpc=Off -d magic_quotes_runtime=Off -d magic_quotes_sybase=Off" # Pass in the path to php so that drush knows which one to use if it # re-launches itself to run subcommands. We will also pass in the php options. # Important note: Any options added here must be removed when Drush processes # a #! (shebang) script. @see drush_adjust_args_if_shebang_script() exec "$php" $php_options "$SCRIPT_PATH" --php="$php" --php-options="$php_options" "$@" drush-5.10.0/drush.bat000066400000000000000000000013471222105546100145420ustar00rootroot00000000000000@echo off rem Drush automatically determines the user's home directory by checking for rem HOME or HOMEDRIVE/HOMEPATH environment variables, and the temporary rem directory by checking for TEMP, TMP, or WINDIR environment variables. rem The home path is used for caching Drush commands and the git --reference rem cache. The temporary directory is used by various commands, including rem package manager for downloading projects. rem You may want to specify a path that is not user-specific here; e.g., to rem keep cache files on the same filesystem, or to share caches with other rem users. rem set HOME=H:/drush rem set TEMP=H:/drush REM See http://drupal.org/node/506448 for more information. @php.exe "%~dp0drush.php" --php="php.exe" %* drush-5.10.0/drush.complete.sh000066400000000000000000000024701222105546100162130ustar00rootroot00000000000000#!/bin/bash # BASH completion script for Drush. # # Place this in your /etc/bash_completion.d/ directory or source it from your # ~/.bash_completion or ~/.bash_profile files. Alternatively, source # examples/example.bashrc instead, as it will automatically find and source # this file. # Ensure drush is available. which drush > /dev/null || alias drush &> /dev/null || return __drush_ps1() { f="${TMPDIR:-/tmp/}/drush-env/drush-drupal-site-$$" if [ -f $f ] then DRUPAL_SITE=$(cat "$f") fi [[ -n "$DRUPAL_SITE" ]] && printf "${1:- (%s)}" "$DRUPAL_SITE" } # Completion function, uses the "drush complete" command to retrieve # completions for a specific command line COMP_WORDS. _drush_completion() { # Set IFS to newline (locally), since we only use newline separators, and # need to retain spaces (or not) after completions. local IFS=$'\n' # The '< /dev/null' is a work around for a bug in php libedit stdin handling. # Note that libedit in place of libreadline in some distributions. See: # https://bugs.launchpad.net/ubuntu/+source/php5/+bug/322214 COMPREPLY=( $(drush --early=includes/complete.inc "${COMP_WORDS[@]}" < /dev/null) ) } # Register our completion function. We include common short aliases for Drush. complete -o nospace -F _drush_completion d dr drush drush5 drush6 drush6 drush.php drush-5.10.0/drush.info000066400000000000000000000000251222105546100147170ustar00rootroot00000000000000drush_version=5.10.0 drush-5.10.0/drush.php000077500000000000000000000140371222105546100145660ustar00rootroot00000000000000#!/usr/bin/env php $command['command'], '!commandfile' => $command['commandfile'])), 'bootstrap'); $command_found = TRUE; // Dispatch the command(s). $return = drush_dispatch($command); // prevent a '1' at the end of the output if ($return === TRUE) { $return = ''; } if (drush_get_context('DRUSH_DEBUG') && !drush_get_context('DRUSH_QUIET')) { drush_print_timers(); } drush_log(dt('Peak memory usage was !peak', array('!peak' => drush_format_size(memory_get_peak_usage()))), 'memory'); break; } } } else { break; } } if (!$command_found) { // If we reach this point, command doesn't fit requirements or we have not // found either a valid or matching command. // If no command was found check if it belongs to a disabled module. if (!$command) { $command = drush_command_belongs_to_disabled_module(); } // Set errors related to this command. $args = implode(' ', drush_get_arguments()); if (isset($command) && is_array($command)) { foreach ($command['bootstrap_errors'] as $key => $error) { drush_set_error($key, $error); } drush_set_error('DRUSH_COMMAND_NOT_EXECUTABLE', dt("The drush command '!args' could not be executed.", array('!args' => $args))); } elseif (!empty($args)) { drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt("The drush command '!args' could not be found. Run `drush cache-clear drush` to clear the commandfile cache if you have installed new extensions.", array('!args' => $args))); } // Set errors that occurred in the bootstrap phases. $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array()); foreach ($errors as $code => $message) { drush_set_error($code, $message); } } return $return; } /** * Check if the given command belongs to a disabled module * * @return * Array with a command-like bootstrap error or FALSE if Drupal was not * bootstrapped fully or the command does not belong to a diabled module. */ function drush_command_belongs_to_disabled_module() { if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE, DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); $commands = drush_get_commands(); $arguments = drush_get_arguments(); $command_name = array_shift($arguments); if (isset($commands[$command_name])) { // We found it. Load its module name and set an error. if (is_array($commands[$command_name]['drupal dependencies']) && count($commands[$command_name]['drupal dependencies'])) { $modules = implode(', ', $commands[$command_name]['drupal dependencies']); } else { // The command does not define Drupal dependencies. Derive them. $command_files = drush_get_context('DRUSH_COMMAND_FILES', array()); $command_path = $commands[$command_name]['path'] . DIRECTORY_SEPARATOR . $commands[$command_name]['commandfile'] . '.drush.inc'; $modules = array_search($command_path, $command_files); } return array( 'bootstrap_errors' => array( 'DRUSH_COMMAND_DEPENDENCY_ERROR' => dt('Command !command needs the following module(s) enabled to run: !dependencies.', array('!command' => $command_name, '!dependencies' => $modules)), ) ); } } return FALSE; } drush-5.10.0/drush_logo-black.png000066400000000000000000000553601222105546100166560ustar00rootroot00000000000000‰PNG  IHDRÚ×|ƒO pHYs  šœtEXtSoftwareAdobe ImageReadyqÉe<Z}IDATxÚì}œ\Wyïw§Ïîl/ZiWÒªÙ*Æ^wËXSû…`!t^’‡C”÷ !È$$”‘ï°.`l‰`c0¶%l¹JV±%ÙÒª¬ÊJÛë´{Þ9wn9õÞ;³³»³Òûjfsçüïÿûþç+”Çœç_úî·Ivwï{ñׇËg§t†V>%œ|Ó·vs#£“zIg½oƒæý͈ß-Ÿí2ÐÎU]„oºL M÷èÂ`ûTù¬—vÖÛ~øèâǺ{Ûwövô w6|f]@ÏÌä!¬Ç`ûqù—˜þ*Ÿ‚™ÿù_Ö<Ú}jÝÞÓÃëþãùëNŽNû#©q˜aeš–G™Ñæþ¨ùè&âw­3ý+r[K?¿´¶âC' ïÕçfúжbF»¶ü •m.ƒk± * `öˆ…°ª±Ö4ÕÀ²ÚÄÃAxöù^è㯂‚Hå~¨‚` w?hÜ×"dß @ÔþÜM*€¬€‰L˜?ÌŽò/UÚ\×k1fYK"«1¸È6?7ö4¦œQqúL¯ðy±‚¦ªŒDÈ|$€*÷!DíÊÝ„²Í‚ hµç_úîÅ奀2Ðæ À¾†o6ð&á*XK°iX‹¸~F2™öE0Ð ãi¶0Ëi(·KÃÿØ`Ã€Õ `9ûˆ-°Yl¨ðÓÊ@+mNŒõdµ±° ¬Õ5Þ²æ¸ÈÇŽ g(5ë1•l›Åp6Øðm$˜Åfdÿ3„·•Â2ÐæÂ0ÌÄ/\µ² 7'““òÇÄEjÆZ&ƒY`³MKk¿JrÏ[( ZÙO›(Ÿ‚)›Fa³BÇé3§¥ûã‘p,&ÓÀùG3ÙJ³Ÿ°öšÌ¦9 µØSa>¶—Å2Ðæ ›yù`¢ÙèÜÍd'ûµŽŸÌclôÐ4°?ƒŒpP—J™ÑÊ@›;@#~Y¡C´HÈ“F1”Ãn5l8 d&ª£!)ÐTÉåQZIŽ‚1{%þâLFÍÂ0p²»ƒÍÚGü´²ùXÚœe´©øh2i?”™Œev*À2°™Äˆÿ Éý´²ùXÚÜ`4kÚ¿æ.íGCÀ€Ê1ÝÁ昋ô³Ž2–VÚf˜•R d3¹´h6“Q6Ûu“€ÍÜ IMÇÎò¯YZÉ›ù²0þ™JÚ7AD1Ÿ'Ø4š½ä`SEˆP¬òÏYZ©ŽÎBü3oiß4-5 lR`©Á¦ñŠ %²`å²ùXÚœc´|GzÈ€ °iÆžÀr›L‰ÔÊëi³0Ê!XEZ¾kh^Ò~$䘌äJ¨bB¯Hcî†Ý'üdÏŒ{4ã"ÉzZ*3·C±¨µ¿Á}/þzghg÷(h C$Ò~ˆU%U`³eÇ>’[ë5"êìGÁ9 ñ[¡mÄ<_ÙTÕñÚé#3B˦;ëz^ AWòõOÊ@+|"ðüÚB€æ%íÇ#CuÔ‘‰M6Ó\´ð–#0l6«åÒm,V‹È%þvR OØ¡™’âÖd9¦Çàª=¶SVòa=>vr¡èœ©ã/m†ÙŒ$tÎfª¨ýœÉG|4=‡0lZ.±Ó2!Ç[¢±jTá£Yßi[ÁDªz­ó’l´UÇ¡"„¶|‹)þõ]/Áà‘=^¿G7\g)š’shdýjA$»Nåñõ”ÉÓ ¹ªN3•[5e!D%í'b3aap™`C¼éäšÑ`)«±©6„ÕØÒÙÀ´ÍL†Ý¨z¾•)o1°*1°Ü÷›œœ„{îƒì¨Ÿ?K,Œ­ldl)­pS迚éHÚaqâ““¼Ÿä[g hóód4/i? LK³fñØqÉ€¸Xºõ…8"7!sïAf„H:[|?Í4©»,&»°¥Æ`'H„¡Tƒ,m³™Œ“§NA×]÷À¤ÄõÛfl?.ÍŸÙÑ N ŽÜOŽ@ý‘ÝJ³mÄ'yÉnÓiBtN•Ñd@ËùgÐuǼ ` évâ'˜r=(„¥ÂìË™AéÅcŠ¿Y—Øu,‚«6ø~?}‘yöùàáGS¿á«DvOe&{ží3tÑ{@Ã?Ö'M“ƒùÑ+# ÷Ÿ„БWüÔ?$?ôŽif7ãøò^¬¦îëÍ¡H8`&tZΙø>/rÉÀ9«)‘Î)ün7š «%áh}õrì_Uø>'´©¸ùÁ‡àµ¯«ß 'AKõ⯑”Á`‹¶à7UÝöRP$%2òcµ“l/¨…w,„ïüñ›àãíˆz)ß"£äD?>MáEЖÖ&¦ †ˆ&Q4ÈùYf¬£lËùalŒ#c“ªMí «sÓ.*ÐÛL@FLÄ[ß¹Ú7Ȉ• æ¾1pG—+È4ÌbÁÔqü2¹¯¦OLÇ·iÕ[ˆ"yÇlÏí` ‚ ÞÜÞ_}ç*¸rQ=,kHÀë^ƒ»ùë©b}ã‚ó&ûNìß^D³ö/›½¹­1¿+[À™ù=ñ¨ðü¼ºÄ"wð˜¬DEAR% ¨Çvv5’et,4Ef¬ÄçªÖ<_§¼ü±Ø…×ßCÎy|e[=|îªåØ, ø<Îù ¦â=÷oVûcH‡@¶oƒ’'±™Á†Áiøû´ãï3kI¨Aö™+–Ø“fçîÝ® «­ À‚úëËÀð¸îæ o2}·uEXk)Hq˜LÁp2 ý'ŽÀX¿|‡‚f èäß!·HaB2æ¥-ŒpN¾‰`?mB—VÅê4ÙmÐdª­ë@3TlÌ©¨¡3}ø°S9ÌÉ"aˆ;ÙkÅVdTÅl0[f䬗ÿÄ?ýü“[ößDÀ@ƒŒ\èvîÙ wÝ粶ƱÞD~´QzúRÕ]ÿä )”l™Â…Á®¯nk€?^¾€yn"“…£08™6€ubt&ñ¾ãGAlô4DGN»š¿—¯¬1Àbl:¹ÕûºñX·ŸËÚÏ“çtsÞ!höcóû8woh"Ãy]k è6ŸYrd¢‰MùcÄT¤TECºV³ž>Ál¿Áhöw¡n‘ýÝÍ‹ Ù¢ó@ W+E’Ù›6Û ûÝþS]é,ÃddìÚ벦êÌ« Ú~ QêÈ-‘¬»O%at"ëgÒ¬/„Ý0ЈͿ~•Qu8fƒéÐà+lŒ@|è¸'¸œ«¼—_“4dä±Î?FN clj ‘ð!ÉáˆÌ|t:6Ï’UMoi‡ÿ}ÃU®r½`*š ½c×.xø÷¹šŠ!}hÌ<~ä 4ú9ˆ6c°Õ” Ø´RÙÂÚ8ÜÒy>TDr?رãGá¶þLùÞõ¨Km&£FÔ:"$ô¤ çL³òb·u~¹Meͪ§_+wÖÓFÊÊ#LOæuNÚšâx‹™À¢A&2›FÝ'«!3ž‹ùot2ƒ˜Õò›íS65Á—]]poS‘Èö;vív5Ãú€qË‚ I€æ|_ú9r~´Ø<¨Á6£ël³´/ýç¯>yÇ ‡¥ ëë?AöS˜˜LªAV ö™`K§ì;6 㓞ìFÌ 2vS«>†Šœ×Xe,ÄÖÓp`Ï.8|`_¾ ¬¹ïÕƒöù•Kéx6Ó9ÀÑLç°šŠÅ䬖cG€d&©¬Ét@VdÕsD£Q¸òÒKá ¼Åb1ÆT´ò눩¸ùÁßÀÉÞ^5ë¡ #2Ý\<¨d@3£XKI€mÆöï|ä“ÿoûÁ®£ƒyƒl~f0™fæj±` ˜’xÀ~.·6€£½x÷:´nóÄo3ýõÀ5« àê˜_k5àc¯0>dt{â)xqÇ®¼ÏEmƒµ&5•!¨ˆMé6K!ÓDtLI]ºßb=WVS ñÍ1ŒÏ˜Ä€KbÀ%ÓAì \EvÀFöK¯´€$^Òúºõ“a†2Æ–ÍNÂÛD:Ê,ç3"0 a‹`ÕGéþœt _ODYÈŽ`U;¾ð ëŸPä¡Ù÷þðÆÖÃãµ<È‚Øç~æ¹à¹_‘ÛûøèZj5¦iƒ}"©ûPaI|˜’Å~Ôýºê¬½p¼qlŽœT¯á<þäÓÐ}ä\ÿŽ·ÂïÞ8tÄóû†1À–¶Õã­ÎX|fT1`“>íÈ|{ná1û©2²/86ج‹ •q-›’òÅköL (ÉK:ŽV’%!‰n<ómVöŠkƒ˜ÍRÆFIU,Ï [¨V|gÉ.l“g T±`ÆÀÐ4ƒ¬æ¾Öõzߨa}þÍËmùȱcpÿ¯‘_íBZ4;RݶsyV³& õB‹ÝœºõšÍvÖ}&´jI´4VÂK¯ž‚Œb•ûÐá£ðýÛæ `Ë5²… Fâ&pf¦±hÎ~„4ªNÆñ"“Ñ̾gÖw² Q“ÐΨ‘DŠˆ|¥˜” úí&Í8b¡DƒI˜ÌF°YIt1SPrLEù1~ *Æd4ÏO0Zï¶Ô3mcZc_©»âáÃãW‘ûŸ¾¼.ÁWyûš†R°éû]Ɉæùñæ×åŠÔ8_,À¬™åü¯Ü>~=Í”ù 52`W•²•Ió¶"†ý'ì‡N¤`l"÷w ‡‚°rI3\yÑb˜×På„PÙ›AÂ¥—Qûä‹ÒÔÛÀÊ×CW´Û§“ºþñ`ní0k€ýœh` âu'ÓNb½ÈŽLña²‹B0Úˆ4bdÓtËüÓÆhoýÛ;îØyb¨ÓÙ5KœàÛh$l}^)ã·ÔæJ®ñ'“.¯”IhwU¡^g×Etn¸×9LGØèò ÚàTß(¼üÚ ~Ý×w\ÑÞ k–·ïzñ”|¾í‹±`³ŽÑa3'¥%g "Ǧ}$ÍÝ|Ú_Ÿ—‡© 2–±}6ö}´/M`†›€d6 i=A-!-‰·4 Tü¹¦òßèýZ X£jYgãœ4I0ÙzdGÆ"+GÇaß™˜èîY‚IC™Uz<ç®htÍy»~!#€Xï¥ú„QÍÙy°‘m~S54×WÁö]GáÌ€Z™lÂÌuÉê…P[·U<¶J} d1Ow~ ŽùèdEçËWsÀ&k¥Ë"T ]Ö&³ç/ˆLmòc“³[40A)ÅúpñÊá%–â-ª§ÖÏDAŸÐt€ ÌH|²VöRÏ Üµã(Lpyóª,®‘I,LPºs¥Æ™ .i$4Àœþb6بn™¥Z†1Û¾õ²¥ðú‘>xõS¦L[b&^p~+œ¿dží i¶IGmV‰ËÜÒ4Æ2bAFgJ; ³Ò`4Û-£Á¦(Åc1£"ï:/å®PÉ‹M§ýÏç~sì—)dýÍ3Uc&TdYY¶ÆèO›!n`?©-QM±(´UV@d^ßvPøŒTFƒ3#š«5޹huQý£ÊJc³Í 4NXá–Ì¿q^{´aöxï°¶H$ [ê ²2Ê,5Œb®åä~z¹X,fÍ6K´A|f4ªAdh\ƒ <Ÿ«â2Iœ÷"œ52Ö°”P!e6jÌÚm^:ûø Î[:Ïdr¢ s<ËL¦›sžC–ìNîk¬ÏÅÞÒ¥ªê3•Dg¢0ÑTͦ߮Öò¼|3­ÈFäLŠ2Ù) b-xWpÖLFpˆ¢ŽM–Lº¬º ®[8V×Õˆ 3Çk®€¦&ùšÆ©AKOP¨NšTk`›§S)ÿ´Œ.wø¨õ7f!Ü™™bm–ÊÉÅZ:ëx¬O¨Iïßù2š¢þ߸Bã;ÂÛÞÑ4…ü¸\ð¼4KÍïNmªÇC=©Å„V¡2g4Ûº¨@úÙ†!‹Žwô À±QÏ@^¸æêÿá°˜©LBözú‘ROÒ@l´§ æ"H ˜õ7 –r[ÐÞ¬¥:ÌË9Mn¶9…ò;qÒ4^Ò”³S˃¦`4mVÿ>ÂØ_i˜u“qº€m‹Élð߇{ ™uOQ‰D¢Ødc»wÄ_“\´÷E2x"·VVèP/ ¹AsË–S@)¬ÿhÀ.¶Ë.Ê<“‚Ë:OÌRv›e´Íæ YÖr“qÃl” Ÿ®*X ©¥°íGÀæ5š›[á¼JŸ;3Œ`<‰¤þˆ}ßo…)¦›&È';pµ¦l1ÆÎ8>¢Ê‡ä‡ò1½´@šYúaº©ë5 ¥Í!·ÎVQÕieBöŒMÀËgú=ßsqÇ[ ¶F^Qª§/+Ï”FV4„ß«¶æ@M=Ñ4Q-„:4יޤs_ú§Š¢[¨bÉêE=8<¥#õ%c2N7£°í4™ ž8qNOx§õ_óæ¤þY‚;Ö—õu.èòÍw‘bÀÌó²Ò7ˆRcÉúHöÉüµk”þÚ‘ÓiÖ_Cê_QK¯Ní ttV1ŸÏ›‹4à¬r6øÌkl ‘Ïÿîú \õå—ª'á4¢c&½©MÆM³i2ΈéH™‡± IîvvŒa²¢A—œ¥`æÁ‘£ ö  =ÌdÔVí}ÆÔ£#>@a› ÔÚ³`ÍO.õç™`Ù ¦$€X1æ¡Sƒb9;"h:&x>æ oyh @³Â܈É–šŒ;0È>\ ¾éŒ5¹ÀÌFºº’ÿïŽð|}]m“Ál²Ñ?’Å[†·¤œÉêØS p#vP7ùÉ*lºm>"ÊtÔ™û³ `tÖ÷hó’1Y[P”R«¶ú™£È Hªâ¢)#ùµü|£Ï ø@á7·Î) ™ƒ˜†äÿì©3ž/^Ò¾ Z,‘>wìL Æ“Y{rp‚UÝ &£Pt“o*òu¢96èt³€ŽÎˆ"³1Ñý<3„ýÄDNÄ I…£r¿&_á!õÒŠ¢›ãè}.ǪÅà «TÆ’i±;£Ýd° 9„MÈ“pl$è¸:â^M©¥e‘aB¦Ó)á7›Ô¡±6ÌÖÑœ0% 6¦£'4:¶Ñ¾Š²QÿÊ«7ãDQæ _ItL‚`Àú´ùȉ&V:âMGÞläX{6u¼Ý:äÿe(ÃlÖXÒ&ã¬ÍÛN ¶v|·ãØØ¸tì&ù­¹y¼qPlN|5"ŒTW†(_ÌÉ®vü/6„5#TNB%5ñuNÒ§ÙQ`EK¦§™“~=íç`€@ƒ QZª¬ˆ¨SQŒ¸‰,Û¯®zÿô !zd~NmÇõ^-§ÎvÓÑðÖ=’Î`í¤/­ã"¹¿Ö;˜†ÁÑ ã‚9¾b™›Ô¼iÇtq¡TE$Ô»gMD]`0nmMµlÀ1ãò¾S3„5G¸h*“Ý¿22í²#’™¢ÖÅ„ôBÓB*•q'”ؘ Ñ’ÿÁáQØ;àãyþy)ýµî“LgyCHöùÚ(Œ™Î5Õu§ê°y_§LGV™„@-Hó Þ¥¿0B<Ä|Èhºq”ûy~³n¦ìM· JpÌZÇOlBž²$’NÓ^U •툿öÆÁÝ‚äOÎ?)ùÝXQ™§ ¨ré4Œj¯ÌC’Ð($Tx“P¶ñàDTinÆ?³ÍGÖTdªùÚ !‰Ù L¥b¹ èWMÌGTaL`&Þ‡MF¬”=5ˆÍÆme ±`Û†ÁÖ™E¨„h]ØPë~°d}­¾º¿&<—Êä~˜êŠ0ëe@§½Ð‘öãŽ9ÙÉ;×ÞÄ3 [z¼SÁÇ+“ôâ9⢧‘dYÂî¢R4Þñ£8*|»iˆg—"Òh ™ÖÙ¸à¼ÍeM!ùû Ñ")5¤‚lïKÂðx†ó}Àu’ÛµïuëÖ1m¿Œ¾¥î#ÊsUèlÙzœÛ>à|7náš&IÇn!À5. A¾Ò‰eÉ? ùd·¼>OÞôé9c>Îz³xÌjIÌj„¢>trbÒ—äOÀÖ{ºÆÇG¤¿I]U˜©f Œ‘(ÛïãjN  5ø`_F Ñs¯Ë-js@H‘³¨Xë“ #nàrÃò1ág7ÀasrQÅL@èYÝŽY­³ZɈ"%“¥TóÑMÿnª‘þ®é hzãiaU<+%Œìg¦ëŒ™¨Ij‘“ÿ˜jÇL@€Yàið@EœïF ¬l,'ªšbÏ3—ÞhB«'Ú¤åÒë$#¬ãIz¨ ^„ýˆ‹//Bâ>îxÁ»!)l„b eqŽÝë}6T ó;¥3¶æóâÐäˆÂÓ8Ó‹Ô óL‚Ñé9ÆÉûºÎö(£"Ct¾¥:"/QåêÔò?'ýÓlÉðŒ_£¹Ž~ÔÁüX )"Y¢=Ÿ ÷tP m=ùçË›áà •/ŠD˜þá;·Á€Œ+ƒö©SW§D¢VÑr9f«‹ Óµqµ5Æç±…KJ@ˆõKØ54ð\FÈ&nI¹š³Ë2µ8_Í=•Ƈièz•¯"*Ž,˜|R_tƒUxÊl8ÿÒwo*V+ FÃfãbSw­hvQ5c{î¥W``P)}~ACXÍ[: DƒƒÞt¶m­24t.eR:f§“³ÆG› ݹ®sÑ#@uÇ9þßBV 6vA:©‰f£m"ÉÝ“>¦d>º‰3BÞž>‰™mTö „ÕjÎy ÝûË'7TD6æÌÆy|»#>rçtû‹r6«ŒjÆ&².Èúv;ZDGq8›Î3šN±“Y­3)24Øt>A”Šð×Î¥ÓË ®ËâÚ’,BËEoJA><<ä²?ß8Hû(óˆS24ùž©3%Ëj³´ybßzRŸŸ4ĸh~-˜|c#½­°ªÞÑm !gÑ–[ø•ONzB³  ÁÆš|ˆÝtú1Ï€ˆJ¡±XLæ“ÉXK÷ $5ó[ý!eƒxеqŠö¡+øÜT 0öûg¥‡K’Õ³Ìf5“ݸÚ\½¸ÁÅdÌÉé/¼,g³šJ B!Û>•Å"â¶ÎäžÑf¥n/b#K9ê#íÓ9· #*ÞQ–³ƒé¢_&øcHˆöGR3y,Xˆr}_³± ÿÌ«6ˆ»¦úT¬¶îœÚ¾Ó#Ž ŽqWk©FÊ“ñÂË»¤Ï×'4kUœÙÆ/ëH TÛT¢LtF@a@JGX ˜¿­«¢FÀÒohÀq ò+ç‡IE?j#*ˆÕüøg(ÿ É2ÐÉ9IcV“ŠŒÏY =Õ}ÆA:æ×(E’ªFØìC‡á`÷‰Ò˜k*OƒF÷‘ÏOzæ> .ÆC”OƱµÏ¼_‘VC+:rQ‘:DœÄü¤ÙIØÏ—Ùè!V Öâ”þ™›(âƒñæc;6ßvÎ ›7¾rr¨Ý`³Å®J£›5TH‚ïÈÎ$gÇÊù:s_çäyv?]Ö@\Àv|<ÇDE>"GT¥øÏ(™oF-L·Ùˆ@®hÒ¬œ×Zœ‚îPvo“JuûœÚ/^9ºžt%"a4·1>> /î "ˆ…=¢<è(´ÆÊñ:³ÆßgÕF‡ÝÄ55æ½T ²N³%—˦ÓnS©Œ¾M8ÅäG^QøAžKÈrVã © •afŸ¾¢kx¦¤@›-QdV€v×ÏŸ¨é7œÓk—6}в¤,µ‘ÉiöîƒÝ{÷K?§®Ü#.¤ žÎÔhÔ¹zS’‰3äÕF܈û_Và³±uÝŸ¹(‰Ø @ÍfÀ³—±­„ p¦$ë»](T>*7bÓ±ùˆP¶dXmV€öøÁÞõúr‹‹kÕ+ÏwÐ<º§·‹]A‚ʨ3Ñtåä×ÊVŒòÈËù:'åë*p!níŒýAD‘‚Kߨø™„=f…¤ “±™;þò7¥ŠgžëwÊ^ †¯6zní…žãË^µ°*YÕV‘"çŸ8%æ©‘Ö»¹š>Nè6]*€è.)*l¤¼NEèÔ›L\Ü"4qÏå°é²(| LºÔ_S‰Hó™DEô]åÇf~Xù4ó+Kç°´ž’†eu`óqñLÏù*®ù覚Ó^’~0Aö¼&7«bfS³ùy.(86«iõ¬kN4ºZÀú5§W´Õ°ÝºÍõ¡Ff{i³É»R¥ãL"„”)4|¾œÊ?F"óëc.ˆ(ç³ÁÅ)ÊxIú¼B³|ª²Š^œÙ¨öϨ¿’Ķh¡¾q[¾{V|ɉLêãXÑPeÏu™¬O~¬—vJDAØ@³#é5îZÉ·¡5¯vVvȵ;ÒT`3é•üM˜ˆ5ó‘0aUyXôdÒ…xFÿ>™ dò52z?’2ÿÞl–ÿš´©U!&*†3¬có1Ü“M$ªr©Y¤ ÆÈå¯uï{ñ×ÛΠm°D™UAw¥íP™Vú‰SlÇIeÉíG&ä4ÜÌ×ÐÌFƒJ›ùá“¶A×ñ<«_¤‡e<ÈÀÈ,-†žxòSå ž^AŠ‹“k *änRÒ{õÔ  hÖè¤Î€ÀƒÏäTÊØÍ(аÙx±‘ã¡ áŸy‰ {÷ÉÍÆDT7Ácþ‹°mJ’måŽ1àrÌIös(°P÷Ù<5eí{… &kRÁVÀBbqU`×É|™‹ÀLi2"OH)€È‰ü³™S›2Æs‹æ—|b>¢¬ªŸµjÔR „ ø,@zu¬ Í›]ØR£A,³‘ŒC‡JD‘bý3 l6#Qû­É QLÄ”ž3I–d•ÍE–]¥…2V ¤ SÆ1 }d~záØ-xØ[ôðÏf2yÈù2ʤ~úeíÍŒ‡pba±æp»¹uJ@Hƒl[­¤ÓÐ ²Y%­v.if²–E0åö¿ºÿ€h6F+¨ìýà˜ 9S”‘-Sã¼9$x`|W[¯)d²Žœb½ •è¡öÉxŸŠïi2R]2üøf~‚–/‹Òåü"7Y_A–dM-=z‚±&„âÓ9ÍySt`'ÛL2ÚFòOkuà-kFÊj´of°™f(‡Ý‡H?$Ò.3À‚ œýÀ™’æs9€SìEÑ­6òÏ#NQš:¹tâDL‘S “¬5!7sQ0,S@½Ã¬kp~½4i‘"ö“Åâà{‘Z^GÒÍ—¤¥þÈ&‡s¿i€TΊ@ Ã÷àað‚c+ò êzÞÞš&öZlþ‘Ns밞란[~û ,oH #·–úHOâC  Å#ކÌB Æ}»‹ µŸb7àhSQCfÅbæ=Ô°ìLG ‘ú9J!€3åCžÏ‰ŸÁ7­G~âÕ­òe3_>šLÒ—Õñ)‚8%çdß [¶›žMdSøÿ! !Šþ\0Reƒ/©ÀP0TÚ‹f:š"G'®vþ5u±0ÔÅ#pp` ˆ¼¿ëÔ±iБmUs,ª­-Ô!`ÉøŒòGˆU\8‡ùrïÑŒõ8 x¦viý-¤îˆ29ßZ‰·mxC2HÜ@æ²^æe2*A&\LÜKɹú®*aÆcÁÚ½b;jÑ牟OA¢ŸÈ¦'Œ€„Ôä°sŽÌ …pƒ/Å[B‘J̆AˆÆký°Zau1°ÞƱ•ð×–ÖU‚ª8,«K[<ì¨>=#poûGÛã£b¤uóþ­Ð3̾ªhê*³ŽG·7£•AMãLoFæ…ž–3šlï£@¤bÍÁY?ª¢iHD2åÀÆ\-9v£nÌ,‡“3RÂtÀ´ÐÔ‹Òà 2ùqr\ãÃ7“Iúʺ'2EXb=Ób‹øœÄlD¯É÷»1š1ÞsÞ˜LÁîÞ!|›†N ñÇÖ4×Àr}`F@EF$˜56"ç“Dë¼E…ú[§ša7?€£LFfŸ@c ÷Õ ™‡¢˜LU”Šù0g‚Ê@æ&äÏfHÂÞÈ[¹u+ú#ëã¤XË‘‡ÔñŸ>k¨8ˆ£Aã­‹›àÆó[ °Ûéè^Úñ¼Þ»Z@d0LPrÀ¢'¼•Êb-Û“_“°›f-:ÓUjÀ©@dz]þ(C¾ÌH¹ò/7yõS¦,úe2¯õ²¼MFÅwMìô#zÈZÓŸŽäb›ƒ§AD}¥Ž”¹Iií'` ãu[æ¢å]€Œl ¿°^ymÑ@$ Â[ÈØtfÊò¤æ¢;¹˜~˜F‰!LÄ"ïäÚ}©å€s]¡Œ†|hÿò* À\ÔCWÑCª.úY±ŒR¦RÐò­8"÷¸F8naXHZ"IMJþi¾C .$5)‰ù«¬“M6È„®ÎŽª–6ÀÔ,Pe™ bé{D­‡q1‡*°1~•• mržýÊœdØ” t…ûi>ZÆJMGÎqc±|A¦X+SMrÕDÌÇd”-N 星p+æøX6óêí&ái1ŽïIîÃlT—ÚSQQB°‚Z ‹€*cË2ûœ©ªÙb˜ƒÈ XÙË´™Èg8Ó北Y¿»9„Æùon4æÌrRˆ¦þÅ5Ï•3ß(£r³¼Ì`ª‰›“É”ò ³bÙX¢‚ú1%l†”¾™ ||e/ê; ·þHa޳EždEøeä´|Œ$ ²ÕÆ `9 â§¢pa(&õ’ ØEÔëÝÙÍæ1 Lìóö³ËIpÅÓœ &ZáHãËp‹HTÐT О¢ÊÉ`nù™2á\M×µH7SÒ]Ud­V £s¡ZÊÆ È+X”¾ÚäØÄ9Ó1péüÜŽ7úG®-ü‡9*&_0Ð"Ih²E;éA¨®$tgLîD">Å]R{ƒVæ˜uþyÎûe›i" ÿ¯ q‹7Š ùÝN 9iŒLÑæ:N½Èî“Æ±c*J$‘t ‰o> s3£|­™¹²Ø©0®n.~U‘ÍdbÇHª:ÿH8׬¢Š ñJŒ ÕÅØ3VÒ … ‡ ¬q,“#: “-ÀäwQ¦ c-Z²å·™9kHÁnRsÒdÚôäÙBãý4ŽU4O÷Ìg= —·H£ò\”FY4½›¤y›‹µÝLG$¿ÀºÄl¹ïV±p×87ÜNbuYÇ”µ®B’XoŸ¬MUJ6ö}þÀ&V6óI p€xsL™4p´?& |S[Hs³Þe>…7Àä‚(có™op1Ü ÑHæ³)ª«ØL|-ò×¼¤/A˜ÖËÂ9à}5ï¥8)ÐH=?tÝGg-›ÍX¡Ãã‡ñ`ñ›-’ 8&!SÂnôO®™EzÀ\cãý844•¨yÊÒDQo?Œuaüùc*¶SÌÏz™jaZ¥2"_·Þl&\Ÿ;›!¤òÙ(®“ÖJA„Ì/‚»ÿÞw-m1îœÌs½¹ÊÌåò©4Ä)ý6þ*ø‘ùoôD`ËlÓ Ü¢_Wè’Ū )b{ž!ö}üw ý0$øYì„TU5I~—tRºÙq•òAÉXÒëfKÁ’Ü4ì‚jŠ\$}Ä/°âˆ|Ž+J*x\WCÙìÂŒ¤~šÈNÖb0Írˆ–ðU¬D›ž¼*É1š&±wU,'ʇÆ4}U©äËLGÈ›íÜLDwóf1åÚR¿WU—R&å#· 1¸Q½8-1‘/)&3—wç׈\7İB¢â'4äås©Ì"9°T`ý7l*ŸËÎ?Êwœöû©‚?²×h*“Q¶8MY¾š‹¦œ¿èá±øé0Aêw ö 2WŸÌÉÀ@yšŒÈ‡œ/Æ4º¯XÛj-í›I¨ùu4w!O„Ô …cr ‘ éÉŒ¤i;Q!­.›~Í?³iJ°¹­—‰¾›#ŽÍV2…’÷Ç “ñ¨C «… °øÑqÑEÐÑÑ---ÌF—_~ٸݱcœ8q^Þñ2¾=é¹¶&š>àÊdRл€ÌË/s%{e0¯ZrñÇfŠÆN´oå«QërðûÃÑD+ê KaWáH\Éh[ç'âphp̈Þ'@ÓusR#˜f°\‘4Á!3%-vcÍIŸ€s |"œÐ”AekÞüf¸æškŒ-‘ðÎû»øâ‹™[÷ƀ{òÉ'á¡ßü8 2"’T1.Ð\T.J{$oª…yÏZb¾™$¢Fºx,a3Pè6ã¿w0Í+^cÜòñŒ¾MGþ@ ÿM™ ‡€/o£›¨FÒÙÍŽ"iÁSQäÌIp.ÿ>šæšüàyEdG¢²®¿þzxÿûß/0V!cþüùð§ú§ÆF€vïÏO>ñŒŒŽ*LÅÂA&Ö(Q¨Œ H ¹[[[áï|'TWUÙ/ß»w/<ûì³0<<ÌT¶èî›!¤.2Á€ŠÄª!­4€%3 }ŽÁ×ZP4U/Èšé(,˜¨¤ŠEm` ‘Äœ‹ï¦œã£9YÈ\š $fóRZ½d(°õë×`²±bÅ ø»¯|F>ÿyp÷Þ{/ŒŽŽp °à¡ª©A¦”ñóñˤ‹êbôÏW\Ÿ¿ù¯àÊ+¯”~W²Ûï¸~tûíøþÓˆ˜*ZJߌRæƒá £„\(Ra .¬"Ž ÚmÿõØâŸìîî~õÌ0üñòùðæ¶&‡î‚Ï>ÿ üá¹g¸5¦4Äà´-Æi ‰¦UÎMÓ4©iFÇGб’|P2yjjlˆåæÜ¢<¤Ÿ5Å3Kÿ¤X·Ür \ÜÑ39FFFàG?ú܃çZÄEøà}!s¹†tÉJÅqehÙžì¯n¾Y 0~ìÁìö¡}†»Øå•›nVQÓ‚˜¥9pE« pM¡@ª”ÁÀ¬½·Í¤ 1ŸÖ}ù.ôøá^xGûø¡ÂÐа`Tðp¸¢*jX;(`íµw ùõOÀÍSc²šýøj|º /”ÐQý^áWR‘NèV|CA5ðBS:ûÄTü2ž<³=ˆ÷ýï}¶mÛfŽ0_9>òîe¦=@ýÇ'>ñ ƒÅª«« ú~kV¯†¿Ù°6n¼U0Eé ÷`8‰ú%Å`«­àt‹ñÕ¸ÐZïØ$yc'9¤¬NÊnk T¦‘%Zäà 6÷ûj¡„ùaJ¢r­ŒDÐÉ€GñŸJÑ÷a/~éK·À 7Ü¥4Þö¶·ÁæK.¯ýë°uÛ6o¿“™(ÒI">\£òE¿¬uÁøçþg¸›‹SŸþ³OÃø£\¶ ’—ò‹&æòÑ[mÃt¸1€Ö\ƒ=ØG;1:a\–û'r&ža [I_©g$sŠÄög°YE ›;»±Ñˆc4·ˆ? s~I´‡õ§o|ãÌd¥2kTUUÁw¾óغu+ÜŠ'°›ŠÉÀñ”øùòãæ>Âb7îs³˜lŸþ4|mãF©ÉHþ:©™ï1º9°hmvCj;OŽNÁŤÔ‰ÁËèfÚŒtra¢ WvÛÊjvÊ ¨MF^²WDƒØa€³@‡¸§^Q!.á¼k–yÍ5o/ùËPꣳ³.½ôRØxë­èÑCa"ª|2ƒ!‰:i°Ø?ý“!z{|àý}íkRF¶²(k8ÃH9k½â=µÇG'?4™Õá…ãýˆ„€,b“1:6{_Û#¼1 ™Ö§0ò¢†—H"ó¯ÄÇj±D&;j2}1ÄÙ êâ¿þ˿ȗDJpãü£ë®3„š]»vA*™ò 2ð2&™øbÿ8üÛ¿þ+,]ºtZ¾O,ƒ‡~z{{…Àa-€Šš6ÁßÂມïÄþ}xKNç¹6šØ>ú¯ŸÜB.r˜Éº ØØß·ï<ƒ“iÓtG&PlÕn1’sí\C¨`x>*6ï*cZZ®îÂ\/…ÏDÜÕT•0DGÉ óŒ¯`&#¦Ù\ùÈGàî»î‚óÎ;Ï7ÈY¿a±;»º a¨˜¦¢l¬]»–ZVp§l¶c¦ÎqÀº3ô³ Û¾tõªŽ‰XW,€î¡1¸í…ðÈÁ“rÓQ‹@6P…W é@¤M0h5¶¤ÖIÀû  Òx# ´€è*wðyÉÄ¿Âx^tÛˆ_F‡F͵±ƒáž»ï†?ÿó?€"” —ùk¼Úgùb˜Å¸ÿþi1eƒI*y‘õ²ÙšÔvúøÆŸßøÂ‰þ.lN /=k´R*º“†pRæ[7jµLžæ$û\ÍJÕç¨_˜×wºï¿0dô³aìÛ·¾ú÷ûöï÷d2»­<ÿ|øÒ-·ÌÀ¬ñ‡?üÞwÓMB¨UUà ˆT±‰ë±éøãe4züdãŸnù‹K–w\»¸y+a·l¸¸ ¶uˆæf x«† Tã¨Fôyxk‚q½Ƴ5Ô+!¥c³…<΋å\Ù À›ù\6¢0ž- #ã| ’Ÿß{/üf7Ãva2~yý—¾øÅe1¹XÇ&º’ö¸%ÇhôøÂwþûƾ°w]ÝїדÕòWmRP¢ÔGaXrrÒ ;A –ûå}÷U@£GÏñãðýï¶lÙâÊhDP!fâÇñ6Ý~˜£½÷}71u>´`ê[;dBH]ɹÒ]úîOB®1!¿ÍÈkŒøRÙ]^Ïç}*ìAyïüñál$ˆw˯~=ö<÷üó6{]~ùåðö·¿Þ·Ù˜5ž6€ö>¦ÿt´²ª—ñ/%1ˆï©ãÊ+àKeÏb.V°¨@L£8DÑˆí¬¹¬•ñ©ÂÚ8ý»eNYƒ·¾õ-p. ¢ìcÆVÊc÷îÝLšqŽI/[gò¸ŠYi†¥m›;Ò±ªŽl(¶žô¨N¤Ç •Nåõ·2(‚™-ÉLË+*ÄzNæ¿åkuÉÅ—@y”óÒEn ¯ID¹ ´|€Hµò…V.†ÅÕ•00xR©$ôõŸ6nûÈm ŽŸû°eQô¤Op¹”-ÕàÑò_C»ä’2ÐJi9r„*ã˜[?“DêÊ"ìç<Ðè1ô³ ‡1غØžì9m­®¶Ñ0Ûæ·´€ 5Køñ–ïÃ`ëa b…‘¼ÁÅ/DkÒÄÎü¾KUU¢<³Kl5ƒŠ-Õ1–h’½lóLW`–ÎÇFãê32§Æ&íINj•èØyÕÍí‹–IŒ¶ -÷Ó ’Ev¸E|øY„öBÎ+ÏìO?ý4UkŒÂ9³m6ÎЫA®I=22§ƒ¡XI0ZhÏ aµõ»Î Á5 š 6fÌ9Âl MP•¨‚‘ÑÎ|¬À>Æ,û25M,ç–bÏÅAÀtçO~¿ôQXÔÚ{×­ƒ›o¾ÚZ[gõ8wïÚÍÔgŒ%𥠛Î(ýR3-V3®,Oa_/ïe›‹EóQÇŒ¦óQQ‚[É8Ѝ©°ÚÙ0’òóÎëÞwb£AFGŠÜÿÀýpã7B×,¯'>ý‡§™’t¥âŸÍ*Ð̱ɠü¾!")(Çd¹^b¹\¸ËVKßHXn!ìtª¡yŸ# öå¯|ì:x`Ëf¶L½¬ðÞ1<2ßøÆ7à‹_ü¢)±Ïüx𡇬É`€,ÕÆsh$cÀrLŸ:~†ñÓr¢‚l>&U?-æ¤BÐ=*\@WˆŸæÌ_|ñ¬Èmßû¼‹lóf±ë%×X„m6‘»÷Kì·}ôc›q°=õôÓ0<ä”+%³±ÍòÕ`ÿàLr­£t3½û¼å"«!-”Õ…Ó.5¦³]Ý;½–cmÇ?+@F€õ®?ú#¸í¶Ûœî;ô•O¨dÅÕÀ§¢åIÓ|ô£pìØ±;þ»ï¾ÛY 5ŠŸJF×lßYšÅjɬÏŸêÚ–’T4Ã|Ô²l4x<ã!¿í–¼I%™Ó*âk¯Á'ׯ‡¯`S1Wˆ -ŠéÆ}ö9¥Abíǽ¯¾ ü'b€n&ÆC¦ÙHþ~e­T­.j ¹Èh6«½ÐÛã˜Õh°©¿¢¢ Z扊–®Eñkbÿ0–¯XSÇz¨³àóBÙV½¹h&þó7¿ 7Ýt<ÿüó€@žiÍ›Šˆ­É˜.ŸFðßøðG>2í`#lFj:’¡‚¯š§ÔÎi ™¬ÖMXíE 6b1Zë-ЭY%¯ò›Öª¥9d.i®¬§Ú@ñøñ­ÏY3ñ'?ù‰h&JTE$€ Ø">HBdúCùð´‚í›ßú¶Íf5 d/™5¤ÔÍf54ËWө-ƒD¥(ŠèÊ\é;>SÖaS² à9ÒâñÈðüêW¿š;fâ§>eš‰ÃvÕ`•/F‡2‰þ˜Šé€é)m€ ³Í?ô!£¢p±Ç]˜ÍŽ=bVQ BEµh›fK±FI­ÆÖ|tÓ!|Ó~mÛ<¸¬¹ž9ÈPHÃ?Ô˰ý…'Å«…>!}PüRšæýE},H{½‚$}Þ{Ï=%[˜‡€ê›ßúlÞ¼Ùö§Ä^zbÆôW\nì 3¥{Žƒcæza©`þ×´ªªÚ8O¤²pQ,¡¡!¸¨ãb|;h~¼ªjšWÈØ¬}¶*±9AX­‹° ´\eË—¯†—wnRkt°ÚˆQg4MÞ¦U,¾¤dxI$DyüÁ~_øÂJdD®'‹ÍÃF¡T$6ÊäÔEª÷¾÷½ð¾÷úˉ$Êâ]]pÇwpÅWÅ‚¬Dlùà?˜Ûš5SþnŸ»ùfd„шÚXª£äâ‹,V»~Ñ|xSc-ó\º;^ÙŽ·ç$¬6 Ál¿ø…<K˃ټÆ×o½n|Ï{JÆ# ;ŽÙǃ‘&S)ÏMØíÃØ¦4ßüo©TU] ¿¸÷Þ)˜ŒŸûÜÍ@w‰VÖCý|)[Îh6µlK h± ¯'7×÷Ž'aYuÂhûKÇ6Ô7Á¾ý»0ñkn¤üÏ?Zæ¢ Hò¹ô¾ÇÜ(ÛF*@ÍÖxî¹çŒ¨›822,oõ̱Ø;Þñøáý,›BaÓ¦¦&¸›q÷Ýw㯠}ËðÍää$lÙò+¸¶³š››óþ[¤Øëg>óH&'©å™Ô8Ä«çÉ"BV6.8o°ïÄþíe 9@[‰oÖ%uöôAƒb~EÜ[82@vò”5î€ÍT®0›"³°_í /œ€ë9Þã `d¢¦¤ðk1ª+·µµÁQlJî}u¯Ð]”é\ƒÿŸÄ !5H:óñËþ[ ¹jÄ ´ÃMM Be­T¹ƒm3Û©²òÑMd9¿oµõñôOä|±¶Ê ¸~ñ|#Â?Ð “NÂ}tIË Ó'±¯–,˜ÑŠuB®½öZø?Øgkƈv"D<úØcF4G¡"÷Áæ5“‹Á7±©øNE?²Gyùýï lûö팠±zÕ*x×»Þ×ဋho¾æ d,›ñ}ÕÈq|÷ßÿÝWS d„Ñð"†ÕÈm¢®j›—Ë>‚”—ëœ a¤¤ ³Ù7ɉ »õkŒfÝãПLÁK§Œ“¸0Q!Ìj!lôœ8"²Z ì°+°<Á4V3'СC‡Œ+v2•2ÌÉbVˆ"2ý¿ãÉIºÁ<ô›‡Œ%¤8Ù4éý“;ï”vÕ$ úèÇ?wß}áw³óÍrïO%“xß1xbÛpß/Æ&XM©ˆ5ø{îÙ»Þxã %Èèe$6#7oÞb,8_zé%Fý|Ùxê©§àfŸn7‘ÛäÄD㵊õHIOãfµßž³Œ†Ùìm`~írXÑ“Êû°¯öÓGàõ¾œIØ‹ìÖR‡û·tÁؘ¤ü\fƒmpʾí—Ñ>™–‡"i·c†»ü²Ë`åÊ•F‰¶|™‹Do2o’œ0Ó4”þ}ƒÑRûO ûÇo|ººº¸º¢lÀ¶Ù}èÁÉþ·¿ý-|æ~ÖÕþ a]Îç.\Øû· ‹.´ŸÔ! '@cÍPñ> <!Òº|-öײSº³Ú–sh/㛎+Ûêác‹Eßç`/üfÿI£­WÏo„e$<òèÒÏ ${° ™*ÜWSSÒ.Ê“ä$ûˆÉD‹&ä~Åz„µHIY±ÍBPü9„Ä¿%«0еe‹ä¿ü_ Ïb6£×½Ø¶¥ýš«®¼Êì­ñÌ3ÏÀ>øAT8™:þT ²õiVŸi ˜Êy/Ëdô~‘ÝÄ«¡yáE²ŸdÆ×ÖB%²¿& #¦âûÖÈ}šk—6Õ ëá§;ŽÀ®SCð‡gà@< õ Ù/FÐëáF¤œýšd†" (>MEMÑdÏ))c)£©%f˜EIYÀ ¹¨¡b/€Y÷ªª«Yþ‹·ÜbÜ pE\knÚúsÏ<ûŒ±­½*×É…D€8lB`²õ>¾l7³ÏdÎWUŒÜ> CgŽ@Mã"þÌ‘u#²zí9㣙åçÈå0öÁ7-„%uêÊRá`.m­ƒ¶ê8¼Ú;C©4 Çj¡b°—Ρ‚d`@ˎϘâ—ÍdDnàñÉ^¼If">Y+#"Yh&‰šGít9‹Ù“¿ P¾1þ‹_ÁÊRñ£h C~šs?9>ñD„‚ªÚÞ¸à<ÀþÚŒDôk%4bû­[Þ€¿^»Â÷ûÆÓ›ÝâCÇ¡æ„<ŽN› ëß:°-‹+éçËhÌ„E>|=¸$ìå0Áßâý0`ÖË%Àe *‰ücºÅ­s¼Ó2Ħúà‰% eÉ¥”ûk3Qã18Ë »Ì`âÏ^¶ªcaßï%ìvY[,¬­€C†¡Ô¸øBÂh(£¸ÄhÓzÅ‘rò! ¨€å—½Dì0JŸ j3QÎb¬§=h‰,'‚L(Ç\”u’ƒŒŒL†”ÉСûl’AÖ׺¦»ãgpAVcÚɵ7œ×‚MÂúü>0¿*oYÒO?»‚YIyñ4ñw³¾@¼˜Eó–}}ŽRºBÀ%¼Ï…Á¯õËbB¢(\¶ƒ(N1€Ä4…ñb7I$ÿXD¢•2­mZUÈÙL“Ù@ìd²fÖ¹¤© YÑ"Ïmß.o”¨§ñ–T$˜ù ›zãòœ_pñé6 OíqÒ{ä½Ü˜|æ.bR\9>Jºí1•ŽÄ¿–€è4P¸œ5ÆTäü1[YdŽ‘f#9“9÷Ù×ÐT̽Çö@6›–ý*ëÏ¿ôÝ7žu@Ãlv‘e2Þ´¦*Âù‰Ÿ8 ‚mO?-ŸÓÉSj@øÊöô¡,æÑ°Ð«Õ¯5Á•ïFƒ›Í—ãq†Ø$Læ½2P"&«8‹­Œ©ãaXLRìˆ19Rç÷+ ÜÕEµ¹(áoÐõ œ<¢tǺÌf,g£iåošW¶ÔæÐÔQo~ð!ùüO 2ü3ÿ“^d0$O"U1’Û税ÃXHHFU‹˜‚õ˜Ú(˜ìjúõb¯pê¸7­fNšŽfÐðzK™6“qâ„sæ×ʼ~­¿Q>O#“¹-d#Ï?D¢>þå;ß¿ÿêWáw¿ûüoÏ>û, +Œ]3ã×Ähs‘˜«¬YW¯] ×cÿë‚ .PIg¹ù¯þJ`±œ0¤fªLñ'áçgÒ“12x*«›í¦¿V´¬lmf”( RþMkÚ¦ÅdÌŒÃl6b¿I ¢¹umÚpæ *W0J|®ÿû׫¹ jEòËèÈ'ìÙ³†HénÉz›ÅÖç`-ÄìµÐô»¼Æ·¾ýmø6Þ\¸• Ñ"ÈÄ+µº˜ß@6¾cuÞr>½fÖu×ÝR6#&#š×—ólÃ$¸t|² *i¬¨P€ ‚$ ûêßýüÙŸý”Ê ÅL¿…Ù”˜ŒrUÑÛóËdô9™Š-BB´._+{ŠDùw˜­¡§4‚3²Å& Ç>uI;´ÕTäm2(“ñÅ;¥&cf¤[rº ¼Ž ëÝšýcª"%Q~êxÁLÊx2Й³nÛOÀ3˜¡®ºê*#éröv|ü“Ÿ€{‡SQ³˜«•3‚~³™$§|N“1âY&QD”Hn,TRþ`Ê@› €¬– ç»fF¦v€Z˜ÞªX˜ÎŒõ`À ©øBóFRÿœ4á¡|ÑëT2‹Sùìögáš·\ï¿éý»«n¢×0’21°¸ ð¡*:·áh%ÔÎ;ß`Ã*ɦ¡ïøk01zÚÈ ( ³-¢lfZÏÕ´šŽfаQµsãÛWCCE~`ü˜ŒÙÔ0dFº~#ÍíkN·ñŒÀÊÌDäÍ„ì$Ì=hkmƒë®»þok×®-ê×!2ýC¿ù ¾ýìÚ½‹‘÷ÝXŒÝ ¢ºjš–É*VÁøÈi8Ó³3MZqŠPQAæb:v›¦ã”D§›ÑŒ‚ùÄ7›Hgózc@ó¡2êYHãf›¤}®‹š¨!mšq†|бü€‹6¹ŽàsõÃýÈØÈ¤& G”H«Ž¢>EhÙ»g÷n[$!ŸuoäÖò»œ‹L=•/fí'%j›—AeM‹òœUT5AÛŠ«áô±½è¦dD inU*§ë‹•…=ÝŒFÖ"¶‚™=MjøñÑüªŒÉáে…¯¡*¢ªÍäP–Ëwõæè×î o”FsÐÏ ‘ütag}yΕÅx?K4ýŒ±á^8ƒ—Õ3PüŽ«M­k ºNZžnc1#D¦U Iîz8»ðz’=}}FG-/„UMUžyg´ÉxÏý@_¿h2&‡ =ÑKãH‰Í ¶u£M]ÞGî gÈç3"Ep k_,¢Y_&€Ñbëè+Rfi.ômUÃb£Šp0ÉϬÃପo…tr R²|Ã)ŒªÚùP?o™ì)ÒKíÃÅü[Ó®:æ 6b2’Ú®*#6'‡B.sZ,-?WiÍ%s}Ö-(–ŸØîàâUJ1.ÑÀ€KÊ´>K¶Ðía*’Λõ Ö`ŸL ÔÔ´jkaddPaÞ!QÛbä“ôIœÅðËæ-¼Ðølnƒèì;±hN-°ñ&ã}¿úoÈfEß.5Òzf̆†£y°˜¶¤ g¡|ÙL,Î#/äq¿€¹˜‰ÎcViÌùb+ŒB¦n,¶fõåpÅï„E‹V`;yòèzVÉnÕõmF' ¡šŠ_¶`Ée²:"düÅtt± k?`óc2fR£Ð@¸ÐˆVdÇ,©†`´´PÌH,Ez¶`ð ~—$'Iµm¤ŸÔ?“dRKÁ™ÀRiÌ™‰« W¯ëeÃ1h¿¨.Y锃«®®ƒeK/€á‘Wv#> )¼S»µ.½3Z¥ì©®éˆÜŸQ y6_ÛžÞ¾]j2N ¼ouÉ4Ö|Ìq¿&¦šÄj–B8ÞÁp® Bñ\ =3A™±>ü,ÒÚ!H™ó!*m~Á函ì×0ñù¬¢zža&Æø|©ãÕ'M0°ðbèÉaïÀ¤²:TG ±EòÅn±ŠZHÔ´@rr$/v#âG¥¼vñËn˜®¹?ã5Cd`[Ý\ u9ó‚téºû©É˜9Žm$?¹CÓ¼…Ï E«!Z½AÑ"  Åȵ֜qPɨ¼¥—”½|™‡¼réf"Šb‡0"ÙËÖŨÑM$ó3K¯¾r¾ÑÊ”®ÇzÆ&`GßœžLMMêcQ_ìFR\òa·š†EP×´Dö”j5zf­Ü/ýÿÍ5+`aM6ï‡}^MÆäŒõà0¤yk^¡´©œ‡·ÞY×›ì‡ôøi|?-“P©À¤l^ჽTæ!û2æ’ Ä#‹Î•µ­Fn—±É”̇¸yðIÈeutÚj`8«ëj`ÞÓõô„çžÒ’¦&ÖH§& ·g‘A­R›Û”ëeÓ^rnVë:ò`ûóž~ô·’ œ‘Ó¯‚NU¹Ò$l¥+ûi*!^»›ˆ‰‚¾[z¢ßز©Q¹àáÅp.U‘‘k ±|ÝL °<ÌzŠVBƒ+VÙèÅ^Ö ¿ï¯‰lÆÄn0AgÇê-­N@GC4G‚ØŽ?äúÇH™‚ÞƒF}kðù‹;To)Z„~É[ý‘!2.^‘Æ@r´W´ùÕ* åY¥8ŠC¼n©ÔTÌwèÙ$¤ÆNcF2.*–“MõPfJMM‰yXÀ{Å«æ&b8êûÂÓmlKsBÊruP3Þ;_zÜ7»Ÿ(ŒŠLjÒDþofbž—D“ lh2 %ÇÎÀØÀ!ó€5æèýt‰ÑòP>"ñÌd®ÅÍIÐa^}GJgÓãïÃæå ÃÎÒÚŽH +¥8"7 Õàò°P„,/‚X¢1ŸŸ³Û4\„y!e¹e•Q}ýІÜ{ ‡Êê&ÈˆÂø©™šã%ÓMæÖo?\ó»GïÛzæÌI)Ç`ë?(Qžkf*Ö#Wíê…©hp;LÒÈn•Ÿd¦»oÈp9¥2iŽøži¼ÑË*sPÅZJñCé¾å¼&ŠBÓ¢Kýš‡E˜_–‹Žô¥àzÞ‘÷[ñq^;“ó»¤:~š·[5i“Øë?$~…<"öe8 †+ ¢®Ý¸uÊ+ yÜëMÀµòÝIÿeÂx¼eñýLrXíË!²öâ_S7ï<¨¨ñ%m5M°é7f²ÜÆH °>¥ë eÓØb£§ý~Ĭtý,) ™“ö"óÇ“‚mR 6u ±üK;Ï…ãµP‰A¦Bn¦â¿Wj³â-ݺ©ž ’I˜ÜFOO.wC4/ÉkZ–^é¦&’sÒeìðlÌ“ëþæ'‹¯Ig6 §Òµ>ÙmÖZë–Ð| _½ÆÉž*ÁÃOCøŠš…«jñ2‡Ö"ýÖàŸEGÏ´×÷@fr¬(çe|ø¾Èt+\;¤—Ÿ52„mçwÊänž •Îïøê7S³w`hC÷ÈØ†žþ3µñžÝRAm6AV²@ó¶Lj O¾Š'G†e)Í[m hAH4®€pÌ5õŸLª‚ó‘°‰syÿ;΃e‘,¼þÆ«pòTôœ™Òyé;ú’Áp^¦¡˜™Œ|¼.wŸ„'ÍkÛÿâsQ²ó…ÕÖ%zl¨ìé ØmVAVÒ@ó ¶¡Þ}†yåé«™7!ì‡UÏ[eD¸Œ)ç"™™7­¨MÀM+œÊQ££ÃàŽ='Oƒ”‹L-cGatà(ÈA‘OÒáHKÚ¦¶7ͺ€0ʳØô—»g˼¥G¨”O1Ùð ëTÈÏu .„Á{ 1AvÕ0|£….™@-¨wÕ*MS±ÑÛä˜áÈ›C•HTÃr²-[e<îï?m°œuK@è6¡ˆ:ÎQâwɨöÏÈãˆ|­¬æÐ0Áu¸TŽG›#W§‹LSNŠRXeû.“£½Ò¯dä35´C,Ñìå(¯+æÕ³š1}ÿòÂePøþAFÇF æÛóêƒùè‘NŽÂ™£;pâŸÉDò\Âը_t–?—G`Ž\ˆÑa‚AüÁT7-7ÙŠ-ÁŠT@MË/éþâi01¤¬æ6ÈQWaÆ›ßÒkV‰KŠ¹È «l›Î•é¦ ݉E,» ÊçjãŽ2\Îr ™`#ެeFJGEͨŠ-`LšHEñ8­tûèõÓ!`ë©ñÂ’ëëå ‰ˆÃ$uéyÉ{ž‰5¦Ç`.çÐ,°™y—ê5$u¾qáeÃÕÑCÙ@|ŽŽi–ªsŒ6\X­‹h$ ‰Ê*)«)›S(À%Ï€–…–ûme¸œ#@£~tÂ@”_ ƒ‹Áz`ÚS#,s«w" “ÿåöhJÆjD²ÌFØä¦aîõºŽ$æ¥óٱʺ2›•Æ€í»¦)™ï$ Nýµ3±¦2ô³ C6Ø 4d@Ã>”?pÉMCu—̲VšlÛÜDÉUyÝ,(g;òD?­NŒœVÔJMÂBÁE 1!yW•2ÐÎe ™`;LC0ûb»LöŽ™ |•úiy*ž‚ˆ!ð¨ÌF™¢þ[të²RšàHß:ɤ ÂIç,F@+Ty$R$,®Á@ÐÝYxQÃ{!jÈôoUß¡<Ê@³À¶œ„ÌN“Å>5›1nØO#Lfõ¼Àæ%ˆä”GKÜðoÒ-hkþK†¤uQ™ÑŠ0BgÛ2™ë»%vX„Ö™^E,ï7“Åk>4+«òk†º5Ð0¯ºŠøÏPgËŒVÅD Ue‚H\.Ãûf.وīÊBHhszL‹ BKñHêwyƒ‹¤¶}Ùl,mÎì§QC©4ܾû %óK!‚ˆ”ˆù(ð•?ö’N†²RÚY0 E”D‰Ü¾çì:“Q´Ìk•˜z µÃÂG¼RÚYÀj–"º•(:<îšå¥<’p©©‚‹CÂe­ ´³l‡ñF‚¢7’Ç»ú†à®×û’ý$‚HDîS<¢r1¤»üË•6WGÂÀ:-S’€MfJz "¤0¨Ïº÷žCõ9¥P  ´ò˜ ؈@ÒN›’¿£,„”v®˜’D(é&KlÏŸÌu;¥‘„DæÉOSD…”…2ÐÎ:°YuQH"xô詜)™ušëS!>Q-Is¾†–ó ªnìeÝå_¦8#T>¥eJâ›÷Z5!‰)I¸ß½t,®®”cߊ°éFÀCûZ´9hÝ…ãÆ{|Ž2Њ4´ò)(Íñ¶ ]7¾Ö?Ü…ͨgù–Ö&c»ýÎÿ˜±c(õÊÄeÓ±<¦<¶mZ¿åúöù­‰¸á'=Ùs~új7TT×ÏÔ!t•…2ÐΉñã¯`çÇWµw®¬«"Ùãƒ$(¹O‹ÎÄŸ&àÞPþʦã97°ßFª5wUôé¨îÝ_lPu›·ÆV^¤.þøÿ ·ÿ*y¢jIEND®B`‚drush-5.10.0/examples/000077500000000000000000000000001222105546100145365ustar00rootroot00000000000000drush-5.10.0/examples/example.aliases.drushrc.php000066400000000000000000000336211222105546100220000ustar00rootroot00000000000000 '/path/to/drupal', * 'uri' => 'dev.mydrupalsite.com', * ); * * With this alias definition, then the following commands * are equivalent: * * $ drush @dev status * $ drush --root=/path/to/drupal --uri=dev.mydrupalsite.com status * * See the --uri option documentation below for hints on setting its value. * * Any option that can be placed on the drush commandline * can also appear in an alias definition. * * There are several ways to create alias files. * * + Put each alias in a separate file called ALIASNAME.alias.drushrc.php * + Put multiple aliases in a single file called aliases.drushrc.php * + Put groups of aliases into files called GROUPNAME.aliases.drushrc.php * * Drush will search for aliases in any of these files using * the alias search path. The following locations are examined * for alias files: * * 1. In any path set in $options['alias-path'] in drushrc.php, * or (equivalently) any path passed in via --alias-path=... * on the command line. * 2. In one of the default locations: * a. /etc/drush * b. $HOME/.drush * c. The sites/all/drush folder for the current Drupal site * 3. Inside the sites folder of any bootstrapped Drupal site, * or any local Drupal site indicated by an alias used as * a parameter to a command * * Folders and files containing other versions of drush in their names will * be *skipped* (e.g. mysite.aliases.drush4rc.php or drush4/mysite.aliases.drushrc.php). * Names containing the current version of drush (e.g. mysite.aliases.drush5rc.php) * will be loaded. * * Files stored in these locations can be used to create aliases * to local and remote Drupal installations. These aliases can be * used in place of a site specification on the command line, and * may also be used in arguments to certain commands such as * "drush rsync" and "drush sql-sync". * * Alias files that are named after the single alias they contain * may use the syntax for the canonical alias shown at the top of * this file, or they may set values in $options, just * like a drushrc.php configuration file: * * $options['uri'] = 'dev.mydrupalsite.com', * $options['root'] = '/path/to/drupal'; * * When alias files use this form, then the name of the alias * is taken from the first part of the alias filename. * * Alias groups (aliases stored together in files called * GROUPNAME.aliases.drushrc.php, as mentioned above) also * create an implicit namespace that is named after the group * name. * * For example: * * # File: mysite.aliases.drushrc.php * $aliases['dev'] = array( * 'root' => '/path/to/drupal', * 'uri' => 'dev.mydrupalsite.com', * ); * $aliases['live'] = array( * 'root' => '/other/path/to/drupal', * 'uri' => 'mydrupalsite.com', * ); * * Then the following special aliases are defined: * * @mysite An alias named after the groupname * may be used to reference all of the * aliases in the group (e.g. drush @mydrupalsite status) * * @mysite.dev A copy of @dev * * @mysite.live A copy of @live * * Thus, aliases defined in an alias group file may be referred to * either by their simple (short) name, or by their full namespace-qualified * name. * * To see an example alias definition for the current bootstrapped * site, use the "site-alias" command with the built-in alias "@self": * * $ drush site-alias @self * * TIP: If you would like to have drush include a 'databases' record * in the output, include the options --with-db and --show-passwords: * * $ drush site-alias @self --with-db --show-passwords * * If you would like to see all of the Drupal sites at a specified * root directory, use the built-in alias "@sites": * * $ drush -r /path/to/drupal site-alias @sites * * It is also possible to define explicit lists of sites using a special * alias list definition. Alias lists contain a list of alias names in * the group, and no other information. For example: * * $aliases['mydevsites'] = array( * 'site-list' => array('@mysite.dev', '@otherside.dev'); * ); * * The built-in alias "@none" represents the state of no Drupal site; * to ignore the site at the cwd and just see default drush status: * * $ drush @none status * * See `drush help site-alias` for more options for displaying site * aliases. See `drush topic docs-bastion` for instructions on configuring * remote access to a Drupal site behind a firewall via a bastion server. * * Although most aliases will contain only a few options, a number * of settings that are commonly used appear below: * * - 'uri': In Drupal 7, the value of --uri should always be the same as * when the site is being accessed from a web browser (e.g. http://mysite.org, * although the http:// is optional). In Drupal 6, the value of --uri should * always be the same as the site's folder name in the 'sites' folder * (e.g. default); it is best if the site folder name matches the * URI from the browser, and is consistent on every instance of the * same site (e.g. also use sites/mysite.org for http://dev.mysite.org). * - 'root': The Drupal root; must not be specified as a relative path. * - 'remote-port': If the database is remote and 'db-url' contains * a tunneled port number, put the actual database port number * used on the remote machine in the 'remote-port' setting. * - 'remote-host': The fully-qualified domain name of the remote system * hosting the Drupal instance. **Important Note: The remote-host option * must be omitted for local sites, as this option controls whether or not * rsync parameters are for local or remote machines. * - 'remote-user': The username to log in as when using ssh or rsync. * - 'os': The operating system of the remote server. Valid values * are 'Windows' and 'Linux'. Be sure to set this value for all remote * aliases because the default value is PHP_OS if 'remote-host' * is not set, and 'Linux' (or $options['remote-os']) if it is. Therefore, * if you set a 'remote-host' value, and your remote OS is Windows, if you * do not set the 'OS' value, it will default to 'Linux' and could cause * unintended consequences, particularly when running 'drush sql-sync'. * - 'ssh-options': If the target requires special options, such as a non- * standard port, alternative identity file, or alternative * authentication method, ssh-options can contain a string of extra * options that are used with the ssh command, eg "-p 100" * - 'parent': The name of a parent alias (e.g. '@server') to use as a basis * for this alias. Any value of the parent will appear in the child * unless overridden by an item with the same name in the child. * Multiple inheritance is possible; name multiple parents in the * 'parent' item separated by commas (e.g. '@server,@devsite'). * - 'db-url': The Drupal 6 database connection string from settings.php. * For remote databases accessed via an ssh tunnel, set the port * number to the tunneled port as it is accessed on the local machine. * If 'db-url' is not provided, then drush will automatically look it * up, either from settings.php on the local machine, or via backend invoke * if the target alias specifies a remote server. * - 'databases': Like 'db-url', but contains the full Drupal 7 databases * record. Drush will look up the 'databases' record if it is not specified. * - 'path-aliases': An array of aliases for common rsync targets. * Relative aliases are always taken from the Drupal root. * '%drush-script': The path to the 'drush' script, or to 'drush.php' or * 'drush.bat', as desired. This is used by backend invoke when drush * runs a drush command. The default is 'drush' on remote machines, or * the full path to drush.php on the local machine. * '%drush': A read-only property: points to the folder that the drush script * is stored in. * '%dump-dir': Path to directory that "drush sql-sync" should use to store * sql-dump files. Helpful filenames are auto-generated. * '%dump': Path to the file that "drush sql-sync" should use to store sql-dump file. * '%files': Path to 'files' directory. This will be looked up if not specified. * '%root': A reference to the Drupal root defined in the 'root' item * in the site alias record. * - 'php': path to custom php interpreter, defaults to /usr/bin/php * - 'php-options': commandline options for php interpreter, you may * want to set this to '-d error_reporting="E_ALL^E_DEPRECATED"' * - 'variables' : An array of name/value pairs which override Drupal variables. * These values take precedence even over settings.php variable overrides. * - 'command-specific': These options will only be set if the alias * is used with the specified command. In the example below, the option * `--no-cache` will be selected whenever the @stage alias * is used in any of the following ways: * drush @stage sql-sync @self @live * drush sql-sync @stage @live * drush sql-sync @live @stage * In case of conflicting options, command-specific options in targets * (source and destination) take precedence over command-specific options * in the bootstrapped site, and command-specific options in a destination * alias will take precedence over those in a source alias. * - 'source-command-specific' and 'target-command-specific': Behaves exactly * like the 'command-specific' option, but is applied only if the alias * is used as the source or target, respectively, of an rsync or sql-sync * command. In the example below, `--skip-tables-list=comments` whenever * the alias @live is the target of an sql-sync command, but comments will * be included if @live is the source for the sql-sync command. * - '#peer': Settings that begin with a '#' are not used directly by Drush, and * in fact are removed before making a backend invoke call (for example). These * kinds of values are useful in conjunction with shell aliases. See * `drush topic docs-shell-aliases` for more information on this. * - rsync command options have specific requirements in order to * be passed through by Drush. See the comments on the sample below: * * 'command-specific' => array ( * 'core-rsync' => array ( * * // single-letter rsync options are placed in the 'mode' key * // instead of adding '--mode=rultvz' to drush rsync command. * 'mode' => 'rultvz', * * // multi-letter rsync options without values must be set to * // TRUE or NULL to work (i.e. setting $VALUE to 1, 0, or '' * // will not work). * 'delete' => TRUE, * * // wrapping an option's value in "" preserves inner '' on output; * // but is not always required. * 'exclude' => "'*.gz'", * * // cannot add multiple options of same key; each key overwrites * // the previous key's value. This 'exclude' option will overwrite * // the previous one above. * 'exclude' => '*.sql', * * // if you need multiple exludes, use an rsync exclude file * 'exclude-from' => "'/etc/rsync/exclude.rules'", * * // filter options with white space must be wrapped in "" to preserve * // the inner ''. * 'filter' => "'exclude *.sql'", * * // if you need multple filter options, see rsync merge-file options * 'filter' => "'merge /etc/rsync/default.rules'", * ), * ), * * Some examples appear below. Remove the leading hash signs to enable. */ #$aliases['stage'] = array( # 'uri' => 'stage.mydrupalsite.com', # 'root' => '/path/to/remote/drupal/root', # 'db-url' => 'pgsql://username:password@dbhost.com:port/databasename', # 'remote-host' => 'mystagingserver.myisp.com', # 'remote-user' => 'publisher', # 'os' => 'Linux', # 'path-aliases' => array( # '%drush' => '/path/to/drush', # '%drush-script' => '/path/to/drush/drush', # '%dump-dir' => '/path/to/dumps/', # '%files' => 'sites/mydrupalsite.com/files', # '%custom' => '/my/custom/path', # ), # 'databases' => # array ( # 'default' => # array ( # 'default' => # array ( # 'driver' => 'mysql', # 'username' => 'sqlusername', # 'password' => 'sqlpassword', # 'port' => '', # 'host' => 'localhost', # 'database' => 'sqldbname', # ), # ), # ), # 'variables' => array( # 'site_name' => 'My Drupal site', # ), # 'command-specific' => array ( # 'sql-sync' => array ( # 'no-cache' => TRUE, # ), # ), # # This shell alias will run `mycommand` when executed via `drush @stage site-specific-alias` # 'shell-aliases' => array ( # 'site-specific-alias' => '!mycommand', # ), # ); #$aliases['dev'] = array( # 'uri' => 'dev.mydrupalsite.com', # 'root' => '/path/to/drupal/root', # 'variables' => array('mail_system' => array('default-system' => 'DevelMailLog')), # ); #$aliases['server'] = array( # 'remote-host' => 'mystagingserver.myisp.com', # 'remote-user' => 'publisher', # 'os' => 'Linux', # ); #$aliases['live'] = array( # 'parent' => '@server,@dev', # 'uri' => 'mydrupalsite.com', # 'target-command-specific' => array ( # 'sql-sync' => array ( # 'skip-tables-list' => 'comments', # ), # ), # ); drush-5.10.0/examples/example.bashrc000066400000000000000000000173701222105546100173650ustar00rootroot00000000000000# -*- mode: shell-script; mode: flyspell-prog; ispell-local-dictionary: "american" -*- # # Example bash aliases to improve your drush experience with bash. # Copy this file to your home directory, rename and customize it to # suit, and source it from your ~/.bash_profile file. # # Example - rename this to .drush_bashrc, and in your # ~/.bash_profile add: # # # if [ -f ~/.drush_bashrc ] ; then # . ~/.drush_bashrc # fi # # Features: # # Finds and sources drush.complete.sh from your drush directory, # enabling autocompletion for drush commands. # # Creates aliases to common drush commands that work in a global context: # # dr - drush # ddd - drush drupal-directory # dl - drush pm-download # ev - drush php-eval # sa - drush site-alias # sa - drush site-alias --local (show local site aliases) # st - drush core-status # use - drush site-set # # Aliases for drush commands that work on the current drupal site: # # cc - drush cache-clear # cca - drush cache-clear all # dis - drush pm-disable # en - drush pm-enable # i - drush pm-info # pml - drush pm-list # rf - drush pm-refresh # unin - drush pm-uninstall # up - drush pm-update # upc - drush pm-updatecode # updb - drush updatedb # q - drush sql-query # # Provides several common shell commands to work better with drush: # # ddd @dev - print the path to the root directory of @dev # cdd @dev - change the current working directory to @dev # lsd @dev - ls root folder of @dev # lsd %files - ls "files" directory of current site # lsd @dev:%devel - ls devel module directory in @dev # @dev st - drush @dev core-status # dssh @live - ssh to the remote server @live points at # gitd @live pull - run `git pull` on the drupal root of @live # # Drush site alias expansion is also done for the cpd command: # # cpd -R @site1:%files @site2:%files # # Note that the 'cpd' alias only works for local sites. Use # `drush rsync` or gitd` to move files between remote sites. # # Aliases are also possible for the following standard # commands. Uncomment their definitions below as desired. # # cd - cddl [*] # ls - lsd # cp - cpd # ssh - dssh # git - gitd # # These standard commands behave exactly the same as they always # do, unless a drush site specification such as @dev or @live:%files # is used in one of the arguments. If you do not want to override # these standard commands, they may be easily removed or commented out # in your copy of this file. # Aliases for common drush commands that work in a global context. alias dr='drush' alias ddd='drush drupal-directory' alias dl='drush pm-download' alias ev='drush php-eval' alias sa='drush site-alias' alias lsa='drush site-alias --local' alias st='drush core-status' alias use='drush site-set' # Aliases for drush commands that work on the current drupal site alias cc='drush cache-clear' alias cca='drush cache-clear all' alias dis='drush pm-disable' alias en='drush pm-enable' alias pmi='drush pm-info' alias pml='drush pm-list' alias rf='drush pm-refresh' alias unin='drush pm-uninstall' alias up='drush pm-update' alias upc='drush pm-updatecode' alias updb='drush updatedb' alias q='drush sql-query' # Overrides for standard shell commands. Uncomment to enable. Alias # cd='cdd' if you want to be able to use cd @remote to ssh to a # remote site. # alias cd='cddl' # alias ls='lsd' # alias cp='cpd' # alias ssh='dssh' # alias git='gitd' # Find the drush executable and test it. d=$(which drush) # If no program is found try an alias. if [ -z "$d" ]; then d=$(alias drush | cut -f 2 -d '=' | sed "s/'//g") fi # Test that drush is an executable. [ -x "$d" ] || exit 0 # If the file found is a symlink, resolve to the actual file. if [ -h "$d" ] ; then # Change `readlink` to `readlink -f` if your drush is a symlink to a symlink. -f is unavailable on OSX's readlink. d=$(readlink $d) fi # Get the directory that drush is stored in. d="${d%/*}" # If we have found drush.complete.sh, then source it. if [ -f "$d/drush.complete.sh" ] ; then . "$d/drush.complete.sh" fi # We extend the cd command to allow convenient # shorthand notations, such as: # cd @site1 # cd %modules # cd %devel # cd @site2:%files # You must use 'cddl' instead of 'cd' if you are not using # the optional 'cd' alias from above. # This is the "local-only" version of the function; # see the cdd function, below, for an expanded implementation # that will ssh to the remote server when a remote site # specification is used. function cddl() { s="$1" if [ -z "$s" ] then builtin cd elif [ "${s:0:1}" == "@" ] || [ "${s:0:1}" == "%" ] then d="$(drush drupal-directory $1 --local 2>/dev/null)" if [ $? == 0 ] then echo "cd $d"; builtin cd "$d"; else echo "Cannot cd to remote site $s" fi else builtin cd "$s"; fi } # Works just like the `cd` shell alias above, with one additional # feature: `cdd @remote-site` works like `ssh @remote-site`, # whereas cd above will fail unless the site alias is local. If # you prefer the `ssh` behavior, you can rename this shell alias # to `cd`. function cdd() { s="$1" if [ -z "$s" ] then builtin cd elif [ "${s:0:1}" == "@" ] || [ "${s:0:1}" == "%" ] then d="$(drush drupal-directory $s 2>/dev/null)" $(drush sa ${s%%:*} --component=remote-host > /dev/null 2>&1) if [ $? != 0 ] then echo "cd $d" builtin cd "$d" else if [ -n "$d" ] then c="cd \"$d\" \; bash" drush -s ${s%%:*} ssh --tty --escaped "$c" drush ${s%%:*} ssh --tty --escaped "$c" else drush ssh ${s%%:*} fi fi else builtin cd "$s" fi } # Allow `git @site gitcommand` as a shortcut for `cd @site; git gitcommand`. # Also works on remote sites, though. function gitd() { s="$1" if [ -n "$s" ] && [ ${s:0:1} == "@" ] || [ ${s:0:1} == "%" ] then d="$(drush drupal-directory $s 2>/dev/null)" $(drush sa ${s%%:*} --component=remote-host > /dev/null 2>&1) if [ $? == 0 ] then drush ${s%%:*} ssh "cd '$d' ; git ${@:2}" else echo cd "$d" \; git "${@:2}" ( cd "$d" "git" "${@:2}" ) fi else "git" "$@" fi } # Get a directory listing on @site or @site:%files, etc, for local or remote sites. function lsd() { p=() r= for a in "$@" ; do if [ ${a:0:1} == "@" ] || [ ${a:0:1} == "%" ] then p[${#p[@]}]="$(drush drupal-directory $a 2>/dev/null)" if [ ${a:0:1} == "@" ] then $(drush sa ${a%:*} --component=remote-host > /dev/null 2>&1) if [ $? == 0 ] then r=${a%:*} fi fi elif [ -n "$a" ] then p[${#p[@]}]="$a" fi done if [ -n "$r" ] then ssh $r ls "${p[@]}" else "ls" "${p[@]}" fi } # Copy files from or to @site or @site:%files, etc; local sites only. function cpd() { p=() for a in "$@" ; do if [ ${a:0:1} == "@" ] || [ ${a:0:1} == "%" ] then p[${#p[@]}]="$(drush drupal-directory $a --local 2>/dev/null)" elif [ -n "$a" ] then p[${#p[@]}]="$a" fi done "cp" "${p[@]}" } # This alias allows `dssh @site` to work like `drush @site ssh`. # Ssh commands, such as `dssh @site ls /tmp`, are also supported. function dssh() { d="$1" if [ ${d:0:1} == "@" ] then drush "$d" ssh "${@:2}" else "ssh" "$@" fi } drush-5.10.0/examples/example.drush.ini000066400000000000000000000046461222105546100200300ustar00rootroot00000000000000; ; Example of a drush php settings override file ; ; IMPORTANT: Before following the instructions in ; this file, first check to see that the cli version ; of php is installed on your system. (e.g. On ; debian systems, `sudo apt-get install php5-cli`.) ; ; Use this file in instances when your system is ; -not- configured to use separate php.ini files for ; webserver and cli use. You can determine which ; php.ini file drush is using by running "drush status". ; If the php.ini file shown is your webserver ini ; file, then rename this file, example.drush.ini, ; to drush.ini and copy it to one of the following ; locations: ; ; 1. Drush installation folder ; 2. User's .drush folder (i.e. ~/.drush/drush.ini) ; 3. System wide configuration folder (i.e. /etc/drush/drush.ini) ; ; If the environment variable DRUSH_INI is defined, ; then the file it specified will be used as drush.ini. ; ; export DRUSH_INI='/path/to/drush.ini' ; ; When in use, the variables defined in this file ; will override the setting values that appear in ; your php.ini file. See the examples below for ; some values that may need to be set in order for ; drush to work. ; ; NOTE: There is a certain amount of overhead ; required for each override, so drush.ini should ; only be used for a relatively small number ; of variables. Comment out any variable that ; has the same value as the webserver php.ini ; to keep the size of the override list small. ; ; To fully specify the value of all php.ini variables, ; copy your webserver php.ini file to one of the ; locations mentioned above (e.g. /etc/drush/php.ini) ; and edit it to suit. Alternately, you may use ; the environment variable PHP_INI to point at ; the file that Drush should use. ; ; export PHP_INI='/path/to/php.ini' ; ; The options listed below are particularly relevant ; to drush. ; ; ; drush needs as much memory as Drupal in order ; to run; make the memory limit setting match ; what you have in your webserver's php.ini. ; memory_limit = 128M ; ; Show all errors and direct them to stderr ; when running drush. ; error_reporting = E_ALL | E_NOTICE | E_STRICT display_errors = stderr ; ; If your php.ini for your webserver is too ; restrictive, you can re-enable functionality ; for drush by adjusting values in this file. ; ; Here are some examples of settings that are ; sometimes set to restrictive values in a ; webserver's php.ini: ; ;safe_mode = ;open_basedir = ;disable_functions = ;disable_classes = drush-5.10.0/examples/example.drushrc.php000066400000000000000000000361171222105546100203630ustar00rootroot00000000000000 TRUE); // Additional folders to search for scripts. // Separate by : (Unix-based systems) or ; (Windows). # $command_specific['script']['script-path'] = 'sites/all/scripts:profiles/myprofile/scripts'; // Always show release notes when running pm-update or pm-updatecode. # $command_specific['pm-update'] = array('notes' => TRUE); # $command_specific['pm-updatecode'] = array('notes' => TRUE); // Set a predetermined username and password when using site-install. # $command_specific['site-install'] = array('account-name' => 'alice', 'account-pass' => 'secret'); /** * List of Drush commands or aliases that should override built-in shell * functions and commands; otherwise, built-ins override Drush commands. Default * is 'help,dd,sa'. Warning: bad things can happen if you put the wrong thing * here (e.g. eval, grep), so be cautious. If a Drush command overrides a * built-in command (e.g. bash help), then you can use the `builtin` operator * to run the built-in version (e.g. `builtin help` to show bash help instead of * Drush help.) If a Drush command overrides a shell command (e.g. grep), then * you can use the regular shell command by typing in the full path to the * command (e.g. /bin/grep). */ # $command_specific['core-cli'] = array('override' => 'help,dd,sa'); /** * Load a drushrc file from the 'drush' folder at the root of the current * git repository. Example script below by grayside. Customize as desired. * @see: http://grayside.org/node/93. */ #exec('git rev-parse --show-toplevel 2> /dev/null', $output); #if (!empty($output)) { # $repo = $output[0]; # $options['config'] = $repo . '/drush/drushrc.php'; # $options['include'] = $repo . '/drush/commands'; # $options['alias-path'] = $repo . '/drush/aliases'; #} drush-5.10.0/examples/example.make000066400000000000000000000066511222105546100170400ustar00rootroot00000000000000; Example makefile ; ---------------- ; This is an example makefile to introduce new users of drush_make to the ; syntax and options available to drush_make. For a full description of all ; options available, see README.txt. ; This make file is a working makefile - try it! Any line starting with a `;` ; is a comment. ; Core version ; ------------ ; Each makefile should begin by declaring the core version of Drupal that all ; projects should be compatible with. core = 7.x ; API version ; ------------ ; Every makefile needs to declare it's Drush Make API version. This version of ; drush make uses API version `2`. api = 2 ; Core project ; ------------ ; In order for your makefile to generate a full Drupal site, you must include ; a core project. This is usually Drupal core, but you can also specify ; alternative core projects like Pressflow. Note that makefiles included with ; install profiles *should not* include a core project. ; Use Pressflow instead of Drupal core: ; projects[pressflow][type] = "core" ; projects[pressflow][download][type] = "file" ; projects[pressflow][download][url] = "http://launchpad.net/pressflow/6.x/6.15.73/+download/pressflow-6.15.73.tar.gz" ; Git clone of Drupal 7.x. Requires the `core` property to be set to 7.x. ; projects[drupal][type] = "core" ; projects[drupal][download][type] = git ; projects[drupal][download][url] = http://git.drupal.org/project/drupal.git projects[] = drupal ; Projects ; -------- ; Each project that you would like to include in the makefile should be ; declared under the `projects` key. The simplest declaration of a project ; looks like this: ; To include the most recent views module: projects[] = views ; This will, by default, retrieve the latest recommended version of the project ; using its update XML feed on Drupal.org. If any of those defaults are not ; desirable for a project, you will want to use the keyed syntax combined with ; some options. ; If you want to retrieve a specific version of a project: ; projects[views] = 2.16 ; Or an alternative, extended syntax: projects[ctools][version] = 1.3 ; Check out the latest version of a project from Git. Note that when using a ; repository as your project source, you must explicitly declare the project ; type so that drush_make knows where to put your project. projects[data][type] = module projects[data][download][type] = git projects[data][download][url] = http://git.drupal.org/project/views.git projects[data][download][revision] = DRUPAL-6--3 ; For projects on drupal.org, some shorthand is available. If any ; download parameters are specified, but not type, the default is git. projects[cck_signup][download][revision] = "2fe932c" ; Specifying 'revision' in the top level is shorthand for download revision. projects[context_admin][revision] = "eb9f05e" ; Clone a project from github. projects[tao][type] = theme projects[tao][download][type] = git projects[tao][download][url] = git://github.com/developmentseed/tao.git ; If you want to install a module into a sub-directory, you can use the ; `subdir` attribute. projects[admin_menu][subdir] = custom ; To apply a patch to a project, use the `patch` attribute and pass in the URL ; of the patch. projects[admin_menu][patch][] = "http://drupal.org/files/issues/admin_menu.long_.31.patch" ; If all projects or libraries share common attributes, the `defaults` ; array can be used to specify these globally, rather than ; per-project. defaults[projects][subdir] = "contrib" drush-5.10.0/examples/helloworld.script000077500000000000000000000025041222105546100201430ustar00rootroot00000000000000#!/usr/bin/env drush // // This example demonstrates how to write a drush // "shebang" script. These scripts start with the // line "#!/usr/bin/env drush" or "#!/full/path/to/drush". // // See `drush topic docs-scripts` for more information. // drush_print("Hello world!"); drush_print(); drush_print("The arguments to this command were:"); // // If called with --everything, use drush_get_arguments // to print the commandline arguments. Note that this // call will include 'php-script' (the drush command) // and the path to this script. // if (drush_get_option('everything')) { drush_print(" " . implode("\n ", drush_get_arguments())); } // // If --everything is not included, then use // drush_shift to pull off the arguments one at // a time. drush_shift only returns the user // commandline arguments, and does not include // the drush command or the path to this script. // else { while ($arg = drush_shift()) { drush_print(' ' . $arg); } } drush_print(); // // We can check which site was bootstrapped via // the '@self' alias, which is defined only if // there is a bootstrapped site. // $self_record = drush_sitealias_get_record('@self'); if (empty($self_record)) { drush_print('No bootstrapped site.'); } else { drush_print('The following site is bootstrapped:'); _drush_sitealias_print_record($self_record); } drush-5.10.0/examples/policy.drush.inc000066400000000000000000000060441222105546100176600ustar00rootroot00000000000000 "Makes a delicious sandwich.", 'arguments' => array( 'filling' => 'The type of the sandwich (turkey, cheese, etc.). Defaults to ascii.', ), 'options' => array( 'spreads' => array( 'description' => 'Comma delimited list of spreads.', 'example-value' => 'mayonnaise,mustard', ), ), 'examples' => array( 'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.', ), 'aliases' => array('mmas'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all. ); // Commandfiles may also add topics. These will appear in // the list of topics when `drush topic` is executed. // To view this topic, run `drush --include=/full/path/to/examples topic` $items['sandwich-exposition'] = array( 'description' => 'Ruminations on the true meaning and philosophy of sandwiches.', 'hidden' => TRUE, 'topic' => TRUE, 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, 'callback' => 'drush_print_file', 'callback arguments' => array(dirname(__FILE__) . '/sandwich-topic.txt'), ); return $items; } /** * Implementation of hook_drush_help(). * * This function is called whenever a drush user calls * 'drush help '. This hook is optional. If a command * does not implement this hook, the command's description is used instead. * * This hook is also used to look up help metadata, such as help * category title and summary. See the comments below for a description. * * @param * A string with the help section (prepend with 'drush:') * * @return * A string with the help text for your command. */ function sandwich_drush_help($section) { switch ($section) { case 'drush:make-me-a-sandwich': return dt("This command will make you a delicious sandwich, just how you like it."); // The 'title' meta item is used to name a group of // commands in `drush help`. If a title is not defined, // the default is "All commands in ___", with the // specific name of the commandfile (e.g. sandwich). // Command files with less than four commands will // be placed in the "Other commands" section, _unless_ // they define a title. It is therefore preferable // to not define a title unless the file defines a lot // of commands. case 'meta:sandwich:title': return dt("Sandwich commands"); // The 'summary' meta item is displayed in `drush help --filter`, // and is used to give a general idea what the commands in this // command file do, and what they have in common. case 'meta:sandwich:summary': return dt("Automates your sandwich-making business workflows."); } } /** * Implementation of drush_hook_COMMAND_validate(). * * The validate command should exit with * `return drush_set_error(...)` to stop execution of * the command. In practice, calling drush_set_error * OR returning FALSE is sufficient. See drush.api.php * for more details. */ function drush_sandwich_make_me_a_sandwich_validate() { if (drush_is_windows()) { // $name = drush_get_username(); // TODO: implement check for elevated process using w32api // as sudo is not available for Windows // http://php.net/manual/en/book.w32api.php // http://social.msdn.microsoft.com/Forums/en/clr/thread/0957c58c-b30b-4972-a319-015df11b427d } else { $name = posix_getpwuid(posix_geteuid()); if ($name['name'] !== 'root') { return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.')); } } } /** * Example drush command callback. This is where the action takes place. * * The function name should be same as command name but with dashes turned to * underscores and 'drush_commandfile_' prepended, where 'commandfile' is * taken from the file 'commandfile.drush.inc', which in this case is 'sandwich'. * Note also that a simplification step is also done in instances where * the commandfile name is the same as the beginning of the command name, * "drush_example_example_foo" is simplified to just "drush_example_foo". * To also implement a hook that is called before your command, implement * "drush_hook_pre_example_foo". For a list of all available hooks for a * given command, run drush in --debug mode. * * If for some reason you do not want your hook function to be named * after your command, you may define a 'callback' item in your command * object that specifies the exact name of the function that should be * called. However, the specified callback function must still begin * with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute") * if you want that all hook functions are still called (e.g. * drush_example_pre_foo_execute, and so on). * * In this function, all of Drupal's API is (usually) available, including * any functions you have added in your own modules/themes. * * @see drush_invoke() * @see drush.api.php */ function drush_sandwich_make_me_a_sandwich($filling = 'ascii') { $str_spreads = ''; // Read options with drush_get_option. Note that the options _must_ // be documented in the $items structure for this command in the 'command' hook. // See `drush topic docs-commands` for more information. if ($spreads = drush_get_option('spreads')) { $list = implode(' and ', explode(',', $spreads)); $str_spreads = ' with just a dash of ' . $list; } $msg = dt('Okay. Enjoy this !filling sandwich!str_spreads.', array('!filling' => $filling, '!str_spreads' => $str_spreads) ); drush_print("\n" . $msg . "\n"); if (drush_get_context('DRUSH_NOCOLOR')) { $filename = dirname(__FILE__) . '/sandwich-nocolor.txt'; } else { $filename = dirname(__FILE__) . '/sandwich.txt'; } drush_print(file_get_contents($filename)); } /** * Command argument complete callback. Provides argument * values for shell completion. * * @return * Array of popular fillings. */ function sandwich_make_me_a_sandwich_complete() { return array('values' => array('turkey', 'cheese', 'jelly', 'butter')); } drush-5.10.0/examples/sandwich.txt000066400000000000000000000172701222105546100171060ustar00rootroot00000000000000 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . .:8 ;t;;t;;;;:..:;%SX888@X%t;.. . . .   . . .. . . . . . .%t%;%@%%%%%%%%%%X@8888XS%t;...:;ttt%X. .  . . . . . . . . .X:8%X%%%XS%%%%%%%XS%%%%%@%%%%X%%%@%S88 .   . . . . . . . . X@ @%%%X8X%%%88%%%8X%%%%%%%%%XXt@8@88@. .  . . . . . . . .t@tS;%%8XSX%@XSX%@XSS%8@@88X@888X8SS S;S.   . . . . . . .@%XS%%%%%S8@X%@8%XXSSXX%S@SSSX888.;@ 888@ . .   . . . . . :.8:S%%%XS8X@@X%S@SSSS8SXSXXX%X88X:;@8@:S  88S. .  . . . .8%S8%%%%%%8@SSSXXXSXSXSXSXSSS8S888 :@%:%XX:%8%:X:   . . .:8 %%%%@%%8@S%%XXSXSSSS8S@X%XSXX88 ;@X;SX88X8;%X88t.   . . 88S%S%%%%8XSSXSX@@S@%XS8@SS%@S%888 88@S:8. .;.@%X:@8;.   . .  88.8888888@XX 888888%X%@XX 88SS8@@;S@8.%;8@S%%:8  .  . .  S%:8 @SSSS8 @@8@8 8 88888888@%S:8:S8 @..%S SXX8888;. .  . . %:8S8888@88SXS S S::X@.8.8 X%S%8X:X88..% @@.S.%% .;. .   . .XX8@8;;%%t;;;;:@X@888888@888888.88S;8:8  ... . . .  . . 8.;;@8@8:%%%%%t.8@%ttX@8@@@S8%8 X8S;X:@; :... . . . . .  . tS:8@;88.;:8888X8S:.tX88888X88  S8tStS88 :.. . . . . . .   .:X;;:t%;tt%888S@8XS888@8.:tt@;88.tXXX8:::... . . . . . . .  .:X8St:8SXS XS8@X 8.8%888%X8@@X88tXS8t; . . . . . . . . .   . :8888.88888888X@@X @ X X%S%;88;8t .. . . . . . . . .  ... ..: . .@@888%St @ @ 8SS 8:; . . . . . . . . . . .  . . . ..::. ..:;;::::. ... . . . . . . . . . . . .  .. . . . . .. . . . . .. . . . . . . . . . . . . .  drush-5.10.0/examples/sync_enable.drush.inc000066400000000000000000000120221222105546100206340ustar00rootroot00000000000000 '/srv/www/drupal', * 'uri' => 'site.com', * 'target-command-specific' => array( * 'sql-sync' => array( * 'enable' => array('devel', 'hacked'), * 'disable' => array('securepages'), * 'permission' => array( * 'authenticated user' => array( * 'add' => array('access devel information', 'access environment indicator'), * 'remove' => 'change own password', * ), * 'anonymous user' => array( * 'add' => 'access environment indicator', * ), * ), * ), * ), * ); * * To use this feature, copy the 'target-command-specific' * item from the example alias above, place it in your development * site aliases, and customize the development module list * to suit. You must also copy the sync_enable.drush.inc * file to a location where Drush will find it, such as * $HOME/.drush. See `drush topic docs-commands` for more * information. */ /** * Implement hook help alter. * * When a hook extends a command with additional options, it must * implement help alter and declare the option(s). Doing so will add * the option to the help text for the modified command, and will also * allow the new option to be specified on the command line. Without * this, Drush will fail with an error when a user attempts to use * the option. */ function sync_enable_drush_help_alter(&$command) { if ($command['command'] == 'sql-sync') { $command['options']['enable'] = "Enable the specified modules in the target database after the sync operation has completed."; $command['options']['disable'] = "Disable the specified modules in the target database after the sync operation has completed."; $command['options']['permission'] = "Add or remove permissions from a role in the target database after the sync operation has completed. The value of this option must be an array, so it may only be specified in a site alias record or drush configuration file. See `drush topic docs-example-sync-extension`."; } } /** * Implement hook post sql sync. * * The post hook is only called if the sql-sync operation completes * without an error. When called, we check to see if the user specified * any modules to enable/disable. If so, we will call pm-enable/pm-disable on each module. */ function drush_sync_enable_post_sql_sync($source = NULL, $destination = NULL) { $modules_to_enable = drush_get_option_list('enable'); if (!empty($modules_to_enable)) { drush_log(dt("Enable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_enable))), 'ok'); drush_invoke_process($destination, 'pm-enable', $modules_to_enable, array('yes' => TRUE)); } $modules_to_disable = drush_get_option_list('disable'); if (!empty($modules_to_disable)) { drush_log(dt("Disable !modules post-sql-sync", array('!modules' => implode(',', $modules_to_disable))), 'ok'); drush_invoke_process($destination, 'pm-disable', $modules_to_disable, array('yes' => TRUE)); } $permissions_table = drush_get_option('permission'); if (!empty($permissions_table)) { foreach ($permissions_table as $role => $actions) { $values = drush_invoke_process($destination, 'sql-query', array("select perm from permission, role where role.name='$role' and role.rid = permission.rid;"), array(), array('integrate' => FALSE)); // Remove the first line and explode $sql_output = preg_replace('/^.+\n/', '', trim($values['output'])); $permissions = explode(', ', $sql_output); $original_permissions = $permissions; if (array_key_exists('add', $actions)) { $permissions_to_add = is_array($actions['add']) ? $actions['add'] : explode(', ', $actions['add']); drush_log(dt("Add !permissions to !role post-sql-sync", array('!permissions' => implode(', ', $permissions_to_add), '!role' => $role)), 'ok'); $permissions = array_unique(array_merge($permissions, $permissions_to_add)); } if (array_key_exists('remove', $actions)) { $permissions_to_remove = is_array($actions['remove']) ? $actions['remove'] : explode(', ', $actions['remove']); drush_log(dt("Remove !permissions from !role post-sql-sync", array('!permissions' => implode(', ', $permissions_to_remove), '!role' => $role)), 'ok'); $permissions = array_diff($permissions, $permissions_to_remove); } if ($permissions != $original_permissions) { $permissions_string = implode(', ', $permissions); $values = drush_invoke_process($destination, 'sql-query', array("update permission, role set perm='$permissions_string' where role.name='$role' and role.rid = permission.rid;"), array(), array('integrate' => FALSE)); } } } } drush-5.10.0/examples/sync_via_http.drush.inc000066400000000000000000000110301222105546100212220ustar00rootroot00000000000000 '/srv/www/drupal', * 'uri' => 'staging.site.com', * 'source-command-specific' => array( * 'sql-sync' => array( * 'http-sync' => 'https://staging.site.com/protected-directory/site-database-dump.sql', * 'http-sync-user' => 'wwwadmin', * 'http-sync-password' => 'secretsecret', * ), * ), * ); * * To use this feature, copy the 'source-command-specific' * item from the example alias above, place it in your staging * site aliases, and custom the access credentials as * necessary. You must also copy the sync_via_http.drush.inc * file to a location where Drush will find it, such as * $HOME/.drush. See `drush topic docs-commands` for more * information. * * IMPORTANT NOTE: This example does not cause the sql dump * to be performed; it is presumed that the dump file already * exists at the provided URL. For a full solution, a web page * that initiated an sql-dump (or perhaps a local sql-sync followed * by an sql-sanitize and then an sql-dump) would be necessary. */ /** * Implement hook help alter. * * When a hook extends a command with additional options, it must * implement help alter and declare the option(s). Doing so will add * the option to the help text for the modified command, and will also * allow the new option to be specified on the command line. Without * this, Drush will fail with an error when a user attempts to use * the option. */ function sync_via_http_drush_help_alter(&$command) { if ($command['command'] == 'sql-sync') { $command['options']['http-sync'] = "Copy the database via http instead of rsync. Value is the url that the existing database dump can be found at."; $command['sub-options']['http-sync']['http-sync-user'] = "Username for the protected directory containing the sql dump."; $command['sub-options']['http-sync']['http-sync-password'] = "Password for the same directory."; } } /** * Implement hook pre sql sync. * * During the pre hook, determine if the http-sync option has been * specified. If it has been, then disable the normal ssh + rsync * dump-and-transfer that sql-sync usually does, and transfer the * database dump via an http download. */ function drush_sync_via_http_pre_sql_sync($source = NULL, $destination = NULL) { $sql_dump_download_url = drush_get_option('http-sync'); if (!empty($sql_dump_download_url)) { $user = drush_get_option('http-sync-user', FALSE); $password = drush_get_option('http-sync-password', FALSE); $source_dump_file = _drush_sync_via_http_download_file($sql_dump_download_url, $user, $password); if ($source_dump_file === FALSE) { return drush_set_error('DRUSH_CANNOT_DOWNLOAD', dt("The URL !url could not be downloaded.", array('!url' => $sql_dump_download_url))); } drush_set_option('target-dump', $source_dump_file); drush_set_option('no-dump', TRUE); drush_set_option('no-sync', TRUE); } } /** * Download a file, optionaly with user authentication, using either wget or * curl, as available. */ function _drush_sync_via_http_download_file($url, $user = FALSE, $password = FALSE, $destination = FALSE, $overwrite = TRUE) { static $use_wget; if ($use_wget === NULL) { $use_wget = drush_shell_exec('which wget'); } $destination_tmp = drush_tempnam('download_file'); if ($use_wget) { if ($user && $password) { drush_shell_exec("wget -q --timeout=30 --user=%s --password=%s -O %s %s", $user, $password, $destination_tmp, $url); } else { drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url); } } else { if ($user && $password) { drush_shell_exec("curl -s -L --connect-timeout 30 --user %s:%s -o %s %s", $user, $password, $destination_tmp, $url); } else { drush_shell_exec("curl -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url); } } if (!drush_get_context('DRUSH_SIMULATE')) { if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) { @file_put_contents($destination_tmp, $file); } if (!drush_file_not_empty($destination_tmp)) { // Download failed. return FALSE; } } if ($destination) { drush_move_dir($destination_tmp, $destination, $overwrite); return $destination; } return $destination_tmp; } drush-5.10.0/examples/xkcd.drush.inc000066400000000000000000000146131222105546100173130ustar00rootroot00000000000000 "Retrieve and display xkcd cartoons.", 'arguments' => array( 'search' => 'Optional argument to retrive the cartoons matching an index number, keyword search or "random". If omitted the latest cartoon will be retrieved.', ), 'options' => array( 'image-viewer' => 'Command to use to view images (e.g. xv, firefox). Defaults to "display" (from ImageMagick).', 'google-custom-search-api-key' => 'Google Custom Search API Key, available from https://code.google.com/apis/console/. Default key limited to 100 queries/day globally.', ), 'examples' => array( 'drush xkcd' => 'Retrieve and display the latest cartoon.', 'drush xkcd sandwich' => 'Retrieve and display cartoons about sandwiches.', 'drush xkcd 123 --image-viewer=eog' => 'Retrieve and display cartoon #123 in eog.', 'drush xkcd random --image-viewer=firefox' => 'Retrieve and display a random cartoon in Firefox.', ), 'aliases' => array('xkcd'), 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all. ); return $items; } /** * Implementation of hook_drush_help(). * * This function is called whenever a drush user calls * 'drush help '. This hook is optional. If a command * does not implement this hook, the command's description is used instead. * * This hook is also used to look up help metadata, such as help * category title and summary. See the comments below for a description. * * @param * A string with the help section (prepend with 'drush:') * * @return * A string with the help text for your command. */ function xkcd_drush_help($section) { switch ($section) { case 'drush:xkcd-fetch': return dt("A command line tool (1) for a web site tool (2), that emulates (badly) a web based tool (3) that emulates (badly) a command line tool (4) to access a web site (5) with awesome geek humor.\n (1) Drush (2) Drupal (3) http://uni.xkcd.com/ (4) BASH (5) http://xkcd.com/"); } } /** * Example drush command callback. This is where the action takes place. * * The function name should be same as command name but with dashes turned to * underscores and 'drush_commandfile_' prepended, where 'commandfile' is * taken from the file 'commandfile.drush.inc', which in this case is 'sandwich'. * Note also that a simplification step is also done in instances where * the commandfile name is the same as the beginning of the command name, * "drush_example_example_foo" is simplified to just "drush_example_foo". * To also implement a hook that is called before your command, implement * "drush_hook_pre_example_foo". For a list of all available hooks for a * given command, run drush in --debug mode. * * If for some reason you do not want your hook function to be named * after your command, you may define a 'callback' item in your command * object that specifies the exact name of the function that should be * called. However, the specified callback function must still begin * with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute") * if you want that all hook functions are still called (e.g. * drush_example_pre_foo_execute, and so on). * * In this function, all of Drupal's API is (usually) available, including * any functions you have added in your own modules/themes. * * @see drush_invoke() * @see drush.api.php * * @param * An optional string with search keyworks, cartoon ID or "random". */ function drush_xkcd_fetch($search = '') { if (empty($search)) { drush_xkcd_display('http://xkcd.com'); } elseif (is_numeric($search)) { drush_xkcd_display('http://xkcd.com/' . $search); } elseif ($search == 'random') { $xkcd_response = @json_decode(file_get_contents('http://xkcd.com/info.0.json')); if (!empty($xkcd_response->num)) { drush_xkcd_display('http://xkcd.com/' . rand(1, $xkcd_response->num)); } } else { // This uses an API key with a limited number of searches per $search_response = @json_decode(file_get_contents('https://www.googleapis.com/customsearch/v1?key=' . drush_get_option('google-custom-search-api-key', 'AIzaSyDpE01VDNNT73s6CEeJRdSg5jukoG244ek') . '&cx=012652707207066138651:zudjtuwe28q&q=' . $search)); if (!empty($search_response->items)) { foreach ($search_response->items as $item) { drush_xkcd_display($item->link); } } else { drush_set_error('DRUSH_XKCD_SEARCH_FAIL', dt('The search failed or produced no results.')); } } } /** * Retrieve and display a table of metadata for an XKCD cartoon, * then retrieve and display the cartoon using a specified image viewer. * * @param * A string with the URL of the cartoon to display. */ function drush_xkcd_display($url) { $xkcd_response = @json_decode(file_get_contents($url . '/info.0.json')); if (!empty($xkcd_response->num)) { $data = (array)$xkcd_response; $data['date'] = $data['year'] . '/' . $data['month'] . '/' . $data['day']; unset($data['safe_title'], $data['news'], $data['link'], $data['year'], $data['month'], $data['day']); drush_print_table(drush_key_value_to_array_table($data)); $img = drush_download_file($data['img']); drush_register_file_for_deletion($img); drush_shell_exec(drush_get_option('image-viewer', 'display') . ' ' . $img); } else { drush_set_error('DRUSH_XKCD_METADATA_FAIL', dt('Unable to retrieve cartoon metadata.')); } } drush-5.10.0/includes/000077500000000000000000000000001222105546100145265ustar00rootroot00000000000000drush-5.10.0/includes/backend.inc000066400000000000000000001465101222105546100166170ustar00rootroot00000000000000>>'); define('DRUSH_BACKEND_OUTPUT_DELIMITER', DRUSH_BACKEND_OUTPUT_START . '%s<< "\\0")); $data['output'] = preg_replace("/$packet_regex/s", '', drush_backend_output_collect(NULL)); } if (drush_get_context('DRUSH_QUIET', FALSE)) { ob_end_clean(); } $result_object = drush_backend_get_result(); if (isset($result_object)) { $data['object'] = $result_object; } $error = drush_get_error(); $data['error_status'] = ($error) ? $error : DRUSH_SUCCESS; $data['log'] = drush_get_log(); // Append logging information // The error log is a more specific version of the log, and may be used by calling // scripts to check for specific errors that have occurred. $data['error_log'] = drush_get_error_log(); // If there is a @self record, then include it in the result $self_record = drush_sitealias_get_record('@self'); if (!empty($self_record)) { $site_context = drush_get_context('site', array()); unset($site_context['config-file']); unset($site_context['context-path']); unset($self_record['loaded-config']); unset($self_record['#name']); $data['self'] = array_merge($site_context, $self_record); } // Return the options that were set at the end of the process. $data['context'] = drush_get_merged_options(); printf("\0" . DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data)); } /** * Callback to collect backend command output. */ function drush_backend_output_collect($string) { static $output = ''; if (is_null($string)) { return $output; } $output .= $string; return $string; } /** * Output buffer functions that discards all output but backend packets. */ function drush_backend_output_discard($string) { $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0")); if (preg_match_all("/$packet_regex/s", $string, $matches)) { return implode('', $matches[0]); } } /** * Output a backend packet if we're running as backend. * * @param packet * The packet to send. * @param data * Data for the command. * * @return * A boolean indicating whether the command was output. */ function drush_backend_packet($packet, $data) { if (drush_get_context('DRUSH_BACKEND')) { $data['packet'] = $packet; $data = json_encode($data); // We use 'fwrite' instead of 'drush_print' here because // this backend packet is out-of-band data. fwrite(STDERR, sprintf(DRUSH_BACKEND_PACKET_PATTERN, $data)); return TRUE; } return FALSE; } /** * Parse output returned from a Drush command. * * @param string * The output of a drush command * @param integrate * Integrate the errors and log messages from the command into the current process. * @param outputted * Whether output has already been handled. * * @return * An associative array containing the data from the external command, or the string parameter if it * could not be parsed successfully. */ function drush_backend_parse_output($string, $backend_options = array(), $outputted = FALSE) { $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)'); preg_match("/$regex/s", $string, $match); if (!empty($match) && $match[1]) { // we have our JSON encoded string $output = $match[1]; // remove the match we just made and any non printing characters $string = trim(str_replace(sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string)); } if (!empty($output)) { $data = json_decode($output, TRUE); if (is_array($data)) { _drush_backend_integrate($data, $backend_options, $outputted); return $data; } } return $string; } /** * Integrate log messages and error statuses into the current * process. * * Output produced by the called script will be printed if we didn't print it * on the fly, errors will be set, and log messages will be logged locally, if * not already logged. * * @param data * The associative array returned from the external command. * @param outputted * Whether output has already been handled. */ function _drush_backend_integrate($data, $backend_options, $outputted) { // In 'integrate' mode, logs and errors have already been handled // by drush_backend_packet (sender) drush_backend_parse_packets (reciever - us) // during incremental output. We therefore do not need to call drush_set_error // or drush_log here. The exception is if the sender is an older version of // Drush (version 4.x) that does not send backend packets, then we will // not have processed the log entries yet, and must print them here. $received_packets = drush_get_context('DRUSH_RECEIVED_BACKEND_PACKETS', FALSE); if (is_array($data['log']) && $backend_options['log'] && (!$received_packets)) { foreach($data['log'] as $log) { $message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message']; if (isset($backend_options['#output-label'])) { $message = $backend_options['#output-label'] . $message; } if (!is_null($log['error']) && $backend_options['integrate']) { drush_set_error($log['error'], $message); } elseif ($backend_options['integrate']) { drush_log($message, $log['type']); } } } // Output will either be printed, or buffered to the drush_backend_output command. // If the output has already been printed, then we do not need to show it again on a failure. if (!$outputted) { if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) { drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output']))); } elseif ($backend_options['output']) { _drush_backend_print_output($data['output'], $backend_options); } } } /** * Supress log message output during backend integrate. */ function _drush_backend_integrate_log($entry) { } /** * Call an external command using proc_open. * * @param cmds * An array of records containing the following elements: * 'cmd' - The command to execute, already properly escaped * 'post-options' - An associative array that will be JSON encoded * and passed to the script being called. Objects are not allowed, * as they do not json_decode gracefully. * 'backend-options' - Options that control the operation of the backend invoke * - OR - * An array of commands to execute. These commands already need to be properly escaped. * In this case, post-options will default to empty, and a default output label will * be generated. * @param data * An associative array that will be JSON encoded and passed to the script being called. * Objects are not allowed, as they do not json_decode gracefully. * * @return * False if the command could not be executed, or did not return any output. * If it executed successfully, it returns an associative array containing the command * called, the output of the command, and the error code of the command. */ function _drush_backend_proc_open($cmds, $process_limit, $context = NULL) { $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to ); $open_processes = array(); $bucket = array(); $process_limit = max($process_limit, 1); $is_windows = drush_is_windows(); // Loop through processes until they all close, having a nap as needed. $nap_time = 0; while (sizeof($open_processes) || sizeof($cmds)) { $nap_time++; if (sizeof($cmds) && (sizeof($open_processes) < $process_limit)) { // Pop the site and command (key / value) from the cmds array end($cmds); list($site, $cmd) = each($cmds); unset($cmds[$site]); if (is_array($cmd)) { $c = $cmd['cmd']; $post_options = $cmd['post-options']; $backend_options = $cmd['backend-options']; } else { $c = $cmd; $post_options = array(); $backend_options = array(); } $backend_options += array( '#output-label' => '', '#process-read-size' => 4096, ); $process = array(); drush_log($backend_options['#output-label'] . $c); $process['process'] = proc_open($c, $descriptorspec, $process['pipes'], null, null, array('context' => $context)); if (is_resource($process['process'])) { if ($post_options) { fwrite($process['pipes'][0], json_encode($post_options)); // pass the data array in a JSON encoded string } // If we do not close stdin here, then we cause a deadlock; // see: http://drupal.org/node/766080#comment-4309936 // If we reimplement interactive commands to also use // _drush_proc_open, then clearly we would need to keep // this open longer. fclose($process['pipes'][0]); $process['info'] = stream_get_meta_data($process['pipes'][1]); stream_set_blocking($process['pipes'][1], FALSE); stream_set_timeout($process['pipes'][1], 1); $bucket[$site]['cmd'] = $c; $bucket[$site]['output'] = ''; $bucket[$site]['remainder'] = ''; $bucket[$site]['backend-options'] = $backend_options; $bucket[$site]['end_of_output'] = FALSE; $bucket[$site]['outputted'] = FALSE; $open_processes[$site] = $process; } // Reset the $nap_time variable as there might be output to process next // time around: $nap_time = 0; } // Set up to call stream_select(). See: // http://php.net/manual/en/function.stream-select.php // We can't use stream_select on Windows, because it doesn't work for // streams returned by proc_open. if (!$is_windows) { $ss_result = 0; $read_streams = array(); $write_streams = array(); $except_streams = array(); foreach ($open_processes as $site => &$current_process) { if (isset($current_process['pipes'][1])) { $read_streams[] = $current_process['pipes'][1]; } } // Wait up to 2s for data to become ready on one of the read streams. if (sizeof($read_streams)) { $ss_result = stream_select($read_streams, $write_streams, $except_streams, 2); // If stream_select returns a error, then fallback to using $nap_time. if ($ss_result !== FALSE) { $nap_time = 0; } } } foreach ($open_processes as $site => &$current_process) { if (isset($current_process['pipes'][1])) { // Collect output from stdout $bucket[$site][1] = ''; $info = stream_get_meta_data($current_process['pipes'][1]); if (!feof($current_process['pipes'][1]) && !$info['timed_out']) { $string = $bucket[$site]['remainder'] . fread($current_process['pipes'][1], $backend_options['#process-read-size']); $bucket[$site]['remainder'] = ''; $output_end_pos = strpos($string, DRUSH_BACKEND_OUTPUT_START); if ($output_end_pos !== FALSE) { $trailing_string = substr($string, 0, $output_end_pos); $trailing_remainder = ''; // If there is any data in the trailing string (characters prior // to the backend output start), then process any backend packets // embedded inside. if (strlen($trailing_string) > 0) { drush_backend_parse_packets($trailing_string, $trailing_remainder, $bucket[$site]['backend-options']); } // If there is any data remaining in the trailing string after // the backend packets are removed, then print it. if (strlen($trailing_string) > 0) { _drush_backend_print_output($trailing_string . $trailing_remainder, $bucket[$site]['backend-options']); $bucket[$site]['outputted'] = TRUE; } $bucket[$site]['end_of_output'] = TRUE; } if (!$bucket[$site]['end_of_output']) { drush_backend_parse_packets($string, $bucket[$site]['remainder'], $bucket[$site]['backend-options']); // Pass output through. _drush_backend_print_output($string, $bucket[$site]['backend-options']); if (strlen($string) > 0) { $bucket[$site]['outputted'] = TRUE; } } $bucket[$site][1] .= $string; $bucket[$site]['output'] .= $string; $info = stream_get_meta_data($current_process['pipes'][1]); flush(); // Reset the $nap_time variable as there might be output to process // next time around: if (strlen($string) > 0) { $nap_time = 0; } } else { fclose($current_process['pipes'][1]); unset($current_process['pipes'][1]); // close the pipe , set a marker // Reset the $nap_time variable as there might be output to process // next time around: $nap_time = 0; } } else { // if both pipes are closed for the process, remove it from active loop and add a new process to open. $bucket[$site]['code'] = proc_close($current_process['process']); unset($open_processes[$site]); // Reset the $nap_time variable as there might be output to process next // time around: $nap_time = 0; } } // We should sleep for a bit if we need to, up to a maximum of 1/10 of a // second. if ($nap_time > 0) { usleep(max($nap_time * 500, 100000)); } } return $bucket; // TODO: Handle bad proc handles //} //return FALSE; } /** * Print the output received from a call to backend invoke, * adding the label to the head of each line if necessary. */ function _drush_backend_print_output($output_string, $backend_options) { if ($backend_options['output'] && !empty($output_string)) { $output_label = array_key_exists('#output-label', $backend_options) ? $backend_options['#output-label'] : FALSE; if (!empty($output_label)) { // Remove one, and only one newline from the end of the // string. Else we'll get an extra 'empty' line. foreach (explode("\n", preg_replace('/\\n$/', '', $output_string)) as $line) { fwrite(STDOUT, $output_label . rtrim($line) . "\n"); } } else { fwrite(STDOUT, $output_string); } } } /** * Parse out and remove backend packet from the supplied string and * invoke the commands. */ function drush_backend_parse_packets(&$string, &$remainder, $backend_options) { $remainder = ''; $packet_regex = strtr(sprintf(DRUSH_BACKEND_PACKET_PATTERN, "([^\0]*)"), array("\0" => "\\0")); if (preg_match_all("/$packet_regex/s", $string, $match, PREG_PATTERN_ORDER)) { drush_set_context('DRUSH_RECEIVED_BACKEND_PACKETS', TRUE); foreach ($match[1] as $packet_data) { $entry = (array) json_decode($packet_data); if (is_array($entry) && isset($entry['packet'])) { $function = 'drush_backend_packet_' . $entry['packet']; if (function_exists($function)) { $function($entry, $backend_options); } else { drush_log(dt("Unknown backend packet @packet", array('@packet' => $entry['packet'])), 'notice'); } } else { drush_log(dt("Malformed backend packet"), 'error'); drush_log(dt("Bad packet: @packet", array('@packet' => print_r($entry, TRUE))), 'debug'); drush_log(dt("String is: @str", array('@str' => $packet_data), 'debug')); } } $string = preg_replace("/$packet_regex/s", '', $string); } // Check to see if there is potentially a partial packet remaining. // We only care about the last null; if there are any nulls prior // to the last one, they would have been removed above if they were // valid drush packets. $embedded_null = strrpos($string, "\0"); if ($embedded_null !== FALSE) { // We will consider everything after $embedded_null to be part of // the $remainder string if: // - the embedded null is less than strlen(DRUSH_BACKEND_OUTPUT_START) // from the end of $string (that is, there might be a truncated // backend packet header, or the truncated backend output start // after the null) // OR // - the embedded null is followed by DRUSH_BACKEND_PACKET_START // (that is, the terminating null for that packet has not been // read into our buffer yet) if (($embedded_null + strlen(DRUSH_BACKEND_OUTPUT_START) >= strlen($string)) || (substr($string, $embedded_null + 1, strlen(DRUSH_BACKEND_PACKET_START)) == DRUSH_BACKEND_PACKET_START)) { $remainder = substr($string, $embedded_null); $string = substr($string, 0, $embedded_null); } } } /** * Backend command for setting errors. */ function drush_backend_packet_set_error($data, $backend_options) { if (!$backend_options['integrate']) { return; } $output_label = ""; if (array_key_exists('#output-label', $backend_options)) { $output_label = $backend_options['#output-label']; } drush_set_error($data['error'], $data['message'], $output_label); } /** * Default options for backend_invoke commands. */ function _drush_backend_adjust_options($site_record, $command, $command_options, $backend_options) { // By default, if the caller does not specify a value for 'output', but does // specify 'integrate' === FALSE, then we will set output to FALSE. Otherwise we // will allow it to default to TRUE. if ((array_key_exists('integrate', $backend_options)) && ($backend_options['integrate'] === FALSE) && (!array_key_exists('output', $backend_options))) { $backend_options['output'] = FALSE; } $result = $backend_options + array( 'method' => 'GET', 'output' => TRUE, 'log' => TRUE, 'integrate' => TRUE, 'backend' => TRUE, 'dispatch-using-alias' => FALSE, ); // Convert '#integrate' et. al. into backend options foreach ($command_options as $key => $value) { if (substr($key,0,1) === '#') { $result[substr($key,1)] = $value; } } return $result; } /** * Execute a new local or remote command in a new process. * * n.b. Prefer drush_invoke_process() to this function. * * @param invocations * An array of command records to exacute. Each record should contain: * 'site': * An array containing information used to generate the command. * 'remote-host' * Optional. A remote host to execute the drush command on. * 'remote-user' * Optional. Defaults to the current user. If you specify this, you can choose which module to send. * 'ssh-options' * Optional. Defaults to "-o PasswordAuthentication=no" * 'path-aliases' * Optional; contains paths to folders and executables useful to the command. * '%drush-script' * Optional. Defaults to the current drush.php file on the local machine, and * to simply 'drush' (the drush script in the current PATH) on remote servers. * You may also specify a different drush.php script explicitly. You will need * to set this when calling drush on a remote server if 'drush' is not in the * PATH on that machine. * 'command': * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. * 'args': * An array of arguments for the command. * 'options' * Optional. An array containing options to pass to the remote script. * Array items with a numeric key are treated as optional arguments to the * command. * 'backend-options': * Optional. Additional parameters that control the operation of the invoke. * 'method' * Optional. Defaults to 'GET'. * If this parameter is set to 'POST', the $data array will be passed * to the script being called as a JSON encoded string over the STDIN * pipe of that process. This is preferable if you have to pass * sensitive data such as passwords and the like. * For any other value, the $data array will be collapsed down into a * set of command line options to the script. * 'integrate' * Optional. Defaults to TRUE. * If TRUE, any error statuses will be integrated into the current * process. This might not be what you want, if you are writing a * command that operates on multiple sites. * 'log' * Optional. Defaults to TRUE. * If TRUE, any log messages will be integrated into the current * process. * 'output' * Optional. Defaults to TRUE. * If TRUE, output from the command will be synchronously printed to * stdout. * 'drush-script' * Optional. Defaults to the current drush.php file on the local * machine, and to simply 'drush' (the drush script in the current * PATH) on remote servers. You may also specify a different drush.php * script explicitly. You will need to set this when calling drush on * a remote server if 'drush' is not in the PATH on that machine. * 'dispatch-using-alias' * Optional. Defaults to FALSE. * If specified as a non-empty value the drush command will be * dispatched using the alias name on the command line, instead of * the options from the alias being added to the command line * automatically. * @param common_options * Optional. Merged in with the options for each invocation. * @param backend_options * Optional. Merged in with the backend options for each invocation. * @param default_command * Optional. Used as the 'command' for any invocation that does not * define a command explicitly. * @param default_site * Optional. Used as the 'site' for any invocation that does not * define a site explicitly. * @param context * Optional. Passed in to proc_open if provided. * * @return * If the command could not be completed successfully, FALSE. * If the command was completed, this will return an associative array containing the data from drush_backend_output(). */ function drush_backend_invoke_concurrent($invocations, $common_options = array(), $common_backend_options = array(), $default_command = NULL, $default_site = NULL, $context = NULL) { $index = 0; // Slice and dice our options in preparation to build a command string $invocation_options = array(); foreach ($invocations as $invocation) { $site_record = isset($invocation['site']) ? $invocation['site'] : $default_site; // NULL is a synonym to '@self', although the latter is preferred. if (!isset($site_record)) { $site_record = '@self'; } // If the first parameter is not a site alias record, // then presume it is an alias name, and try to look up // the alias record. if (!is_array($site_record)) { $site_record = drush_sitealias_get_record($site_record); } $command = isset($invocation['command']) ? $invocation['command'] : $default_command; $args = isset($invocation['args']) ? $invocation['args'] : array(); $command_options = isset($invocation['options']) ? $invocation['options'] : array(); $backend_options = isset($invocation['backend-options']) ? $invocation['backend-options'] : array(); // If $backend_options is passed in as a bool, interpret that as the value for 'integrate' if (!is_array($common_backend_options)) { $integrate = (bool)$common_backend_options; $common_backend_options = array('integrate' => $integrate); } $command_options += $common_options; $backend_options += $common_backend_options; $backend_options = _drush_backend_adjust_options($site_record, $command, $command_options, $backend_options); // Insure that contexts such as DRUSH_SIMULATE and NO_COLOR are included. $command_options += _drush_backend_get_global_contexts($site_record); // If the caller has requested it, don't pull the options from the alias // into the command line, but use the alias name for dispatching. if (!empty($backend_options['dispatch-using-alias']) && isset($site_record['#name'])) { list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options(array(), $command_options, $backend_options); $site_record_to_dispatch = '@' . ltrim($site_record['#name'], '@'); } else { list($post_options, $commandline_options, $drush_global_options) = _drush_backend_classify_options($site_record, $command_options, $backend_options); $site_record_to_dispatch = ''; } $site_record += array('path-aliases' => array()); $site_record['path-aliases'] += array( '%drush-script' => NULL, ); $site = (array_key_exists('#name', $site_record) && !array_key_exists($site_record['#name'], $invocation_options)) ? $site_record['#name'] : $index++; $invocation_options[$site] = array( 'site-record' => $site_record, 'site-record-to-dispatch' => $site_record_to_dispatch, 'command' => $command, 'args' => $args, 'post-options' => $post_options, 'drush-global-options' => $drush_global_options, 'commandline-options' => $commandline_options, 'command-options' => $command_options, 'backend-options' => $backend_options, ); } // Calculate the length of the longest output label $max_name_length = 0; $label_separator = ''; if (!array_key_exists('no-label', $common_options) && (count($invocation_options) > 1)) { $label_separator = array_key_exists('#label-separator', $common_options) ? $common_options['#label-separator'] : ' >> '; foreach ($invocation_options as $site => $item) { $backend_options = $item['backend-options']; if (!array_key_exists('#output-label', $backend_options)) { if (is_numeric($site)) { $backend_options['#output-label'] = ' * [@self.' . $site; $label_separator = '] '; } else { $backend_options['#output-label'] = $site; } $invocation_options[$site]['backend-options']['#output-label'] = $backend_options['#output-label']; } $name_len = strlen($backend_options['#output-label']); if ($name_len > $max_name_length) { $max_name_length = $name_len; } if (array_key_exists('#label-separator', $backend_options)) { $label_separator = $backend_options['#label-separator']; } } } // Now pad out the output labels and add the label separator. $reserve_margin = $max_name_length + strlen($label_separator); foreach ($invocation_options as $site => $item) { $backend_options = $item['backend-options'] + array('#output-label' => ''); $invocation_options[$site]['backend-options']['#output-label'] = str_pad($backend_options['#output-label'], $max_name_length, " ") . $label_separator; if ($reserve_margin) { $invocation_options[$site]['drush-global-options']['reserve-margin'] = $reserve_margin; } } // Now take our prepared options and generate the command strings $cmds = array(); foreach ($invocation_options as $site => $item) { $site_record = $item['site-record']; $site_record_to_dispatch = $item['site-record-to-dispatch']; $command = $item['command']; $args = $item['args']; $post_options = $item['post-options']; $commandline_options = $item['commandline-options']; $command_options = $item['command-options']; $drush_global_options = $item['drush-global-options']; $backend_options = $item['backend-options']; $os = drush_os($site_record); // If the caller did not pass in a specific path to drush, then we will // use a default value. For commands that are being executed on the same // machine, we will use DRUSH_COMMAND, which is the path to the drush.php // that is running right now. For remote commands, we will run a wrapper // script instead of drush.php -- drush.bat on Windows, or drush on Linux. $drush_path = $site_record['path-aliases']['%drush-script']; $php = array_key_exists('php', $site_record) ? $site_record['php'] : (array_key_exists('php', $command_options) ? $command_options['php'] : NULL); $drush_command_path = drush_build_drush_command($drush_path, $php, $os, array_key_exists('remote-host', $site_record)); $cmd = _drush_backend_generate_command($site_record, $drush_command_path . " " . _drush_backend_argument_string($drush_global_options, $os) . " " . $site_record_to_dispatch . " " . $command, $args, $commandline_options, $backend_options) . ' 2>&1'; $cmds[$site] = array( 'cmd' => $cmd, 'post-options' => $post_options, 'backend-options' => $backend_options, ); } return _drush_backend_invoke($cmds, $common_backend_options, $context); } /** * Find all of the drush contexts that are used to cache global values and * return them in an associative array. */ function _drush_backend_get_global_contexts($site_record) { $result = array(); $global_option_list = drush_get_global_options(FALSE); foreach ($global_option_list as $global_key => $global_metadata) { if (is_array($global_metadata)) { $value = ''; if (!array_key_exists('never-propagate', $global_metadata)) { if ((array_key_exists('propagate-cli-value', $global_metadata))) { $value = drush_get_option($global_key, '', 'cli'); } elseif ((array_key_exists('context', $global_metadata))) { // If the context is declared to be a 'local-context-only', // then only put it in if this is a local dispatch. if (!array_key_exists('local-context-only', $global_metadata) || !array_key_exists('remote-host', $site_record)) { $value = drush_get_context($global_metadata['context'], array()); } } if (!empty($value)) { $result[$global_key] = $value; } } } } return $result; } /** * Take all of the values in the $command_options array, and place each of * them into one of the following result arrays: * * - $post_options: options to be encoded as JSON and written to the * standard input of the drush subprocess being executed. * - $commandline_options: options to be placed on the command line of the drush * subprocess. * - $drush_global_options: the drush global options also go on the command * line, but appear before the drush command name rather than after it. * * Also, this function may modify $backend_options. */ function _drush_backend_classify_options($site_record, $command_options, &$backend_options) { // In 'POST' mode (the default, remove everything (except the items marked 'never-post' // in the global option list) from the commandline options and put them into the post options. // The post options will be json-encoded and sent to the command via stdin $global_option_list = drush_get_global_options(FALSE); // These should be in the command line. $additional_global_options = array(); if (array_key_exists('additional-global-options', $backend_options)) { $additional_global_options = $backend_options['additional-global-options']; $command_options += $additional_global_options; } $method_post = ((!array_key_exists('method', $backend_options)) || ($backend_options['method'] == 'POST')); $post_options = array(); $commandline_options = array(); $drush_global_options = array(); $drush_local_options = array(); $additional_backend_options = array(); foreach ($site_record as $key => $value) { if (!in_array($key, drush_sitealias_site_selection_keys())) { if ($key[0] == '#') { $backend_options[$key] = $value; } if (!isset($command_options[$key])) { $command_options[$key] = $value; } } } if (array_key_exists('drush-local-options', $backend_options)) { $drush_local_options = $backend_options['drush-local-options']; $command_options += $drush_local_options; } if (!empty($backend_options['backend']) && empty($backend_options['interactive']) && empty($backend_options['fork'])) { $drush_global_options['backend'] = '2'; } if (!empty($backend_options['interactive'])) { $drush_global_options['invoke'] = TRUE; } foreach ($command_options as $key => $value) { $global = array_key_exists($key, $global_option_list); $propagate = TRUE; $special = FALSE; if ($global) { $propagate = (!array_key_exists('never-propagate', $global_option_list[$key])); $special = (array_key_exists('never-post', $global_option_list[$key])); if ($propagate) { // We will allow 'merge-pathlist' contexts to be propogated. Right now // these are all 'local-context-only' options; if we allowed them to // propogate remotely, then we would need to get the right path separator // for the remote machine. if (is_array($value) && array_key_exists('merge-pathlist', $global_option_list[$key])) { $value = implode(PATH_SEPARATOR, $value); } } } // Just remove options that are designated as non-propagating if ($propagate === TRUE) { // In METHOD POST, move command options to post options if ($method_post && ($special === FALSE)) { $post_options[$key] = $value; } // In METHOD GET, ignore options with array values elseif (!is_array($value)) { if ($global || array_key_exists($key, $additional_global_options)) { $drush_global_options[$key] = $value; } else { $commandline_options[$key] = $value; } } } } return array($post_options, $commandline_options, $drush_global_options, $additional_backend_options); } /** * Create a new pipe with proc_open, and attempt to parse the output. * * We use proc_open instead of exec or others because proc_open is best * for doing bi-directional pipes, and we need to pass data over STDIN * to the remote script. * * Exec also seems to exhibit some strangeness in keeping the returned * data intact, in that it modifies the newline characters. * * @param cmd * The complete command line call to use. * @param post_options * An associative array to json-encode and pass to the remote script on stdin. * @param backend_options * Options for the invocation. * * @return * If the command could not be completed successfully, FALSE. * If one command was executed, this will return an associative array containing * the data from drush_backend_output(). * If multiple commands were executed, this will return an associative array * containing one item, 'concurrent', which will contain a list of the different * backend invoke results from each concurrent command. */ function _drush_backend_invoke($cmds, $common_backend_options = array(), $context = NULL) { if (drush_get_context('DRUSH_SIMULATE') && !array_key_exists('override-simulated', $common_backend_options)) { foreach ($cmds as $cmd) { drush_print(dt('Simulating backend invoke: !cmd', array('!cmd' => $cmd['cmd']))); } return FALSE; } foreach ($cmds as $cmd) { drush_log(dt('Backend invoke: !cmd', array('!cmd' => $cmd['cmd'])), 'command'); } if (array_key_exists('interactive', $common_backend_options) || array_key_exists('fork', $common_backend_options)) { foreach ($cmds as $cmd) { $exec_cmd = $cmd['cmd']; if (array_key_exists('fork', $common_backend_options)) { $exec_cmd .= ' --quiet &'; } drush_log(dt("executing !cmd", array('!cmd' => $exec_cmd))); $ret = drush_shell_proc_open($exec_cmd); } return $ret; } else { $process_limit = drush_get_option_override($common_backend_options, 'concurrency', 1); $procs = _drush_backend_proc_open($cmds, $process_limit, $context); $procs = is_array($procs) ? $procs : array($procs); $ret = array(); foreach ($procs as $site => $proc) { if (($proc['code'] == DRUSH_APPLICATION_ERROR) && isset($common_backend_options['integrate'])) { drush_set_error('DRUSH_APPLICATION_ERROR', dt("The external command could not be executed due to an application error.")); } if ($proc['output']) { $values = drush_backend_parse_output($proc['output'], $proc['backend-options'], $proc['outputted']); $values['site'] = $site; if (is_array($values)) { if (empty($ret)) { $ret = $values; } elseif (!array_key_exists('concurrent', $ret)) { $ret = array('concurrent' => array($ret, $values)); } else { $ret['concurrent'][] = $values; } } else { $ret = drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("The command could not be executed successfully (returned: !return, code: !code)", array("!return" => $proc['output'], "!code" => $proc['code']))); } } } } return empty($ret) ? FALSE : $ret; } /** * Helper function that generates an anonymous site alias specification for * the given parameters. */ function drush_backend_generate_sitealias($backend_options) { // Ensure default values. $backend_options += array( 'remote-host' => NULL, 'remote-user' => NULL, 'ssh-options' => NULL, 'drush-script' => NULL, ); return array( 'remote-host' => $backend_options['remote-host'], 'remote-user' => $backend_options['remote-user'], 'ssh-options' => $backend_options['ssh-options'], 'path-aliases' => array( '%drush-script' => $backend_options['drush-script'], ), ); } /** * Generate a command to execute. * * @param site_record * An array containing information used to generate the command. * 'remote-host' * Optional. A remote host to execute the drush command on. * 'remote-user' * Optional. Defaults to the current user. If you specify this, you can choose which module to send. * 'ssh-options' * Optional. Defaults to "-o PasswordAuthentication=no" * 'path-aliases' * Optional; contains paths to folders and executables useful to the command. * '%drush-script' * Optional. Defaults to the current drush.php file on the local machine, and * to simply 'drush' (the drush script in the current PATH) on remote servers. * You may also specify a different drush.php script explicitly. You will need * to set this when calling drush on a remote server if 'drush' is not in the * PATH on that machine. * @param command * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. * @param args * An array of arguments for the command. * @param command_options * Optional. An array containing options to pass to the remote script. * Array items with a numeric key are treated as optional arguments to the * command. This parameter is a reference, as any options that have been * represented as either an option, or an argument will be removed. This * allows you to pass the left over options as a JSON encoded string, * without duplicating data. * @param backend_options * Optional. An array of options for the invocation. * @see drush_backend_invoke for documentation. * * @return * A text string representing a fully escaped command. */ function _drush_backend_generate_command($site_record, $command, $args = array(), $command_options = array(), $backend_options = array()) { $drush_path = null; $php = ''; $site_record += array( 'remote-host' => NULL, 'remote-user' => NULL, 'ssh-options' => NULL, 'path-aliases' => array(), ); $hostname = $site_record['remote-host']; $username = $site_record['remote-user']; $ssh_options = $site_record['ssh-options']; $os = drush_os($site_record); if (drush_is_local_host($hostname)) { $hostname = null; } foreach ($command_options as $key => $arg) { if (is_numeric($key)) { $args[] = $arg; unset($command_options[$key]); } } $cmd[] = $command; foreach ($args as $arg) { $cmd[] = drush_escapeshellarg($arg, $os); } $option_str = _drush_backend_argument_string($command_options, $os); if (!empty($option_str)) { $cmd[] = " " . $option_str; } $command = implode(' ', array_filter($cmd, 'strlen')); if (!is_null($hostname)) { if (drush_is_windows($os)) { if (!is_null($username)) { $username = " -u:" . drush_escapeshellarg($username, "LOCAL"); if (array_key_exists('winrs-password', $site_record)) { $username .= " -p:" . drush_escapeshellarg($site_record['winrs-password'], "LOCAL"); } } $command = "winrs" . $username . " -r:" . drush_escapeshellarg($hostname, "LOCAL") . " " . drush_escapeshellarg($command, "LOCAL"); } else { $username = (!is_null($username)) ? drush_escapeshellarg($username, "LOCAL") . "@" : ''; $ssh_options = $site_record['ssh-options']; $ssh_options = (!is_null($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no"); $ssh_cmd[] = "ssh"; $ssh_cmd[] = $ssh_options; $ssh_cmd[] = $username . drush_escapeshellarg($hostname, "LOCAL"); $ssh_cmd[] = drush_escapeshellarg($command . ' 2>&1', "LOCAL"); // Remove NULLs and separate with spaces $command = implode(' ', array_filter($ssh_cmd, 'strlen')); } } return $command; } /** * Map the options to a string containing all the possible arguments and options. * * @param data * Optional. An array containing options to pass to the remote script. * Array items with a numeric key are treated as optional arguments to the command. * This parameter is a reference, as any options that have been represented as either an option, or an argument will be removed. * This allows you to pass the left over options as a JSON encoded string, without duplicating data. * @param method * Optional. Defaults to 'GET'. * If this parameter is set to 'POST', the $data array will be passed to the script being called as a JSON encoded string over * the STDIN pipe of that process. This is preferable if you have to pass sensitive data such as passwords and the like. * For any other value, the $data array will be collapsed down into a set of command line options to the script. * @return * A properly formatted and escaped set of arguments and options to append to the drush.php shell command. */ function _drush_backend_argument_string($data, $os = NULL) { $options = array(); foreach ($data as $key => $value) { if (!is_array($value) && !is_object($value) && !is_null($value)) { if (substr($key,0,1) != '#') { $options[$key] = $value; } } } $option_str = ''; foreach ($options as $key => $value) { $option_str .= _drush_escape_option($key, $value, $os); } return $option_str; } /** * Return a properly formatted and escaped command line option * * @param key * The name of the option. * @param value * The value of the option. * * @return * If the value is set to TRUE, this function will return " --key" * In other cases it will return " --key='value'" */ function _drush_escape_option($key, $value = TRUE, $os = NULL) { if ($value !== TRUE) { $option_str = " --$key=" . drush_escapeshellarg($value, $os); } else { $option_str = " --$key"; } return $option_str; } /** * Read options fron STDIN during POST requests. * * This function will read any text from the STDIN pipe, * and attempts to generate an associative array if valid * JSON was received. * * @return * An associative array of options, if successfull. Otherwise FALSE. */ function _drush_backend_get_stdin() { $fp = fopen('php://stdin', 'r'); // Windows workaround: we cannot count on stream_get_contents to // return if STDIN is reading from the keyboard. We will therefore // check to see if there are already characters waiting on the // stream (as there always should be, if this is a backend call), // and if there are not, then we will exit. // This code prevents drush from hanging forever when called with // --backend from the commandline; however, overall it is still // a futile effort, as it does not seem that backend invoke can // successfully write data to that this function can read, // so the argument list and command always come out empty. :( // Perhaps stream_get_contents is the problem, and we should use // the technique described here: // http://bugs.php.net/bug.php?id=30154 // n.b. the code in that issue passes '0' for the timeout in stream_select // in a loop, which is not recommended. // Note that the following DOES work: // drush ev 'print(json_encode(array("test" => "XYZZY")));' | drush status --backend // So, redirecting input is okay, it is just the proc_open that is a problem. if (drush_is_windows()) { // Note that stream_select uses reference parameters, so we need variables (can't pass a constant NULL) $read = array($fp); $write = NULL; $except = NULL; // Question: might we need to wait a bit for STDIN to be ready, // even if the process that called us immediately writes our parameters? // Passing '100' for the timeout here causes us to hang indefinitely // when called from the shell. $changed_streams = stream_select($read, $write, $except, 0); // Return on error (FALSE) or no changed streams (0). // Oh, according to http://php.net/manual/en/function.stream-select.php, // stream_select will return FALSE for streams returned by proc_open. // That is not applicable to us, is it? Our stream is connected to a stream // created by proc_open, but is not a stream returned by proc_open. if ($changed_streams < 1) { return FALSE; } } stream_set_blocking($fp, FALSE); $string = stream_get_contents($fp); fclose($fp); if (trim($string)) { return json_decode($string, TRUE); } return FALSE; } drush-5.10.0/includes/batch.inc000066400000000000000000000065521222105546100163120ustar00rootroot00000000000000uid) { $options['u'] = $user->uid; } drush_include_engine('drupal', 'batch', drush_drupal_major_version()); _drush_backend_batch_process($command, $args, $options); } /** * Process sets from the specified batch. * * This function is called by the worker process that is spawned by the * drush_backend_batch_process function. * * The command called needs to call this function after it's special bootstrap * requirements have been taken care of. */ function drush_batch_command($id) { include_once(DRUSH_DRUPAL_CORE . '/includes/batch.inc'); drush_include_engine('drupal', 'batch', drush_drupal_major_version()); _drush_batch_command($id); } drush-5.10.0/includes/bootstrap.inc000066400000000000000000001144061222105546100172440ustar00rootroot00000000000000 '_drush_bootstrap_drush', DRUSH_BOOTSTRAP_DRUPAL_ROOT => '_drush_bootstrap_drupal_root', DRUSH_BOOTSTRAP_DRUPAL_SITE => '_drush_bootstrap_drupal_site', DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION => '_drush_bootstrap_drupal_configuration', DRUSH_BOOTSTRAP_DRUPAL_DATABASE => '_drush_bootstrap_drupal_database', DRUSH_BOOTSTRAP_DRUPAL_FULL => '_drush_bootstrap_drupal_full', DRUSH_BOOTSTRAP_DRUPAL_LOGIN => '_drush_bootstrap_drupal_login'); $result = array(); if ($init_phases_only) { foreach (array(DRUSH_BOOTSTRAP_DRUSH, DRUSH_BOOTSTRAP_DRUPAL_FULL) as $phase) { $result[$phase] = $functions[$phase]; } } else { $result = $functions; } if (!$function_names) { $result = array_keys($result); } return $result; } /** * @} End of Drush bootstrap phases. */ /** * Bootstrap Drush to the desired phase. * * This function will sequentially bootstrap each * lower phase up to the phase that has been requested. * * @param phase * The bootstrap phase to bootstrap to. * Any of the following constants : * DRUSH_BOOTSTRAP_DRUSH = Only Drush. * DRUSH_BOOTSTRAP_DRUPAL_ROOT = Find a valid Drupal root. * DRUSH_BOOTSTRAP_DRUPAL_SITE = Find a valid Drupal site. * DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION = Load the site's settings. * DRUSH_BOOTSTRAP_DRUPAL_DATABASE = Initialize the database. * DRUSH_BOOTSTRAP_DRUPAL_FULL = Initialize Drupal fully. * DRUSH_BOOTSTRAP_DRUPAL_LOGIN = Log into Drupal with a valid user. */ function drush_bootstrap($phase, $phase_max = FALSE) { static $phases; if (!$phases) { $phases = _drush_bootstrap_phases(TRUE); } static $phase_index = 0; drush_set_context('DRUSH_BOOTSTRAPPING', TRUE); while ($phase >= $phase_index && isset($phases[$phase_index])) { if (drush_bootstrap_validate($phase_index)) { $current_phase = $phases[$phase_index]; if (function_exists($current_phase) && !drush_get_error()) { drush_log(dt("Drush bootstrap phase : !function()", array('!function' => $current_phase)), 'bootstrap'); $current_phase(); // Find any command files that are available during this bootstrap phase. _drush_find_commandfiles($phase_index, $phase_max); } drush_set_context('DRUSH_BOOTSTRAP_PHASE', $phase_index); } else { $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array()); foreach ($errors as $code => $message) { drush_set_error($code, $message); } } unset($phases[$phase_index++]); } drush_set_context('DRUSH_BOOTSTRAPPING', FALSE); return !drush_get_error(); } /** * Determine whether a given bootstrap phase has been completed * * This function name has a typo which makes me laugh so we choose not to * fix it. Take a deep breath, and smile. See * http://en.wikipedia.org/wiki/HTTP_referer * * * @param phase * The bootstrap phase to test * * @returns * TRUE if the specified bootstrap phase has completed. */ function drush_has_boostrapped($phase) { $phase_index = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); return isset($phase_index) && ($phase_index >= $phase); } /** * Validate whether a bootstrap phase can be reached. * * This function will validate the settings that will be used * during the actual bootstrap process, and allow commands to * progressively bootstrap to the highest level that can be reached. * * This function will only run the validation function once, and * store the result from that execution in a local static. This avoids * validating phases multiple times. * * @param phase * The bootstrap phase to validate to. * Any of the following constants : * DRUSH_BOOTSTRAP_DRUSH = Only Drush. * DRUSH_BOOTSTRAP_DRUPAL_ROOT = Find a valid Drupal root. * DRUSH_BOOTSTRAP_DRUPAL_SITE = Find a valid Drupal site. * DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION = Load the site's settings. * DRUSH_BOOTSTRAP_DRUPAL_DATABASE = Initialize the database. * DRUSH_BOOTSTRAP_DRUPAL_FULL = Initialize Drupal fully. * DRUSH_BOOTSTRAP_DRUPAL_LOGIN = Log into Drupal with a valid user. * * @return * True if bootstrap is possible, False if the validation failed. * */ function drush_bootstrap_validate($phase) { static $phases; static $result_cache = array(); if (!$phases) { $phases = _drush_bootstrap_phases(TRUE); } static $phase_index = 0; // Check to see if anyone has changed --root or --uri _drush_bootstrap_select_drupal_site(); if (!array_key_exists($phase, $result_cache)) { drush_set_context('DRUSH_BOOTSTRAP_ERRORS', array()); drush_set_context('DRUSH_BOOTSTRAP_VALUES', array()); while ($phase >= $phase_index && isset($phases[$phase_index])) { $current_phase = $phases[$phase_index] . '_validate'; if (function_exists($current_phase)) { $result_cache[$phase_index] = $current_phase(); } else { $result_cache[$phase_index] = TRUE; } drush_set_context('DRUSH_BOOTSTRAP_VALIDATION_PHASE', $phase_index); unset($phases[$phase_index++]); } } return $result_cache[$phase]; } /** * Bootstrap to the specified phase. * * @param $max_phase_index * Only attempt bootstrap to the specified level. */ function drush_bootstrap_to_phase($max_phase_index) { // If $max_phase_index is DRUSH_BOOTSTRAP_MAX, then // we will bootstrap as far as we can. drush_bootstrap_max // is different than drush_bootstrap_to_phase in that // it is not an error if DRUSH_BOOTSTRAP_LOGIN is not reached. if ($max_phase_index == DRUSH_BOOTSTRAP_MAX) { drush_bootstrap_max(); return TRUE; } drush_log(dt("Bootstrap to phase !phase.", array('!phase' => $max_phase_index)), 'bootstrap'); $phases = _drush_bootstrap_phases(); $result = TRUE; // Try to bootstrap to the maximum possible level, without generating errors foreach ($phases as $phase_index) { if ($phase_index > $max_phase_index) { // Stop trying, since we achieved what was specified. break; } if (drush_bootstrap_validate($phase_index)) { if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { $result = drush_bootstrap($phase_index, $max_phase_index); } } else { $result = FALSE; break; } } return $result; } /** * Bootstrap to the highest level possible, without triggering any errors. * * @param $max_phase_index * Only attempt bootstrap to the specified level. * * @return int * The maximum phase to which we bootstrapped. */ function drush_bootstrap_max($max_phase_index = FALSE) { $phases = _drush_bootstrap_phases(); $phase_index = DRUSH_BOOTSTRAP_DRUSH; if (!$max_phase_index) { $max_phase_index = count($phases); } // Try to bootstrap to the maximum possible level, without generating errors foreach ($phases as $phase_index) { if ($phase_index > $max_phase_index) { // Stop trying, since we achieved what was specified. break; } if (drush_bootstrap_validate($phase_index)) { if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { drush_bootstrap($phase_index, $max_phase_index); } } else { break; } } return drush_get_context('DRUSH_BOOTSTRAP_PHASE'); } /** * Bootstrap the specified site alias. The site alias must * be a valid alias to a local site. * * @param $site_record * The alias record for the given site alias. * @see drush_sitealias_get_record(). * @param $max_phase_index * Only attempt bootstrap to the specified level. * @returns TRUE if attempted to bootstrap, or FALSE * if no bootstrap attempt was made. */ function drush_bootstrap_max_to_sitealias($site_record, $max_phase_index = NULL) { if ((array_key_exists('root', $site_record) && !array_key_exists('remote-host', $site_record))) { drush_sitealias_set_alias_context($site_record); drush_bootstrap_max($max_phase_index); return TRUE; } return FALSE; } /** * Helper function to collect any errors that occur during the bootstrap process. * Always returns FALSE, for convenience. */ function drush_bootstrap_error($code, $message = null) { $errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS'); $errors[$code] = $message; drush_set_context('DRUSH_BOOTSTRAP_ERRORS', $errors); return FALSE; } /** * Validate that Drush is running in a suitable environment. */ function _drush_bootstrap_drush_validate() { // @todo _drush_environment_php_ini_checks() always returns TRUE. $return = _drush_environment_check_php_ini(); if ($return !== TRUE) { return $return; } if (drush_environment_lib() === FALSE) { return FALSE; } if (drush_environment_table_lib() === FALSE) { return FALSE; } return TRUE; } /** * Initial Drush bootstrap phase. * * During the initialization of Drush, * this is the first step where all we are * aware of is Drush itself. * * In this step we will register the shutdown function, * parse the command line arguments and store them in their * related contexts. * * Configuration files (drushrc.php) that are * a) Specified on the command line * b) Stored in the root directory of drush.php * c) Stored in the home directory of the system user. * * Additionally the DRUSH_QUIET and DRUSH_BACKEND contexts, * will be evaluated now, as they need to be set very early in * the execution flow to be able to take affect/ */ function _drush_bootstrap_drush() { // Create an alias '@none' to represent no Drupal site _drush_sitealias_cache_alias('none', array('root' => '', 'uri' => '')); // Set the terminal width, used for wrapping table output. // Normally this is exported using tput in the drush script. // If this is not present we do an additional check using stty here. // On Windows in CMD and PowerShell is this exported using mode con. // PHP version prior to 5.3.0 is not supporting this, they always // return 80 columns if (!($columns = getenv('COLUMNS'))) { // Trying to export the columns using stty. exec('stty size 2>&1', $columns_output, $columns_status); if (!$columns_status) $columns = preg_replace('/\d+\s(\d+)/', '$1', $columns_output[0], -1, $columns_count); // If stty fails and Drush us running on Windows are we trying with mode con. if (($columns_status || !$columns_count) && drush_is_windows() && version_compare(phpversion(), '5.3.0') > -1) { $columns_output = array(); exec('mode con', $columns_output, $columns_status); if (!$columns_status && is_array($columns_output)) { $columns = (int)preg_replace('/\D/', '', $columns_output[4], -1, $columns_count); } else { drush_log(dt('Drush could not detect the console window width. Set a Windows Environment Variable of COLUMNS to the desired width.'), 'warning'); } } // Failling back to default columns value if (empty($columns)) { $columns = 80; } } // If a caller wants to reserve some room to add additional // information to the drush output via post-processing, the // --reserve-margin flag can be used to declare how much // space to leave out. This only affects drush functions // such as drush_print_table that wrap the output. $columns -= drush_get_option('reserve-margin', 0); drush_set_context('DRUSH_COLUMNS', $columns); // Statically define a way to call drush again. define('DRUSH_COMMAND', drush_find_drush()); // prime the CWD cache drush_cwd(); // Set up base environment for system-wide file locations. _drush_bootstrap_base_environment(); // Load a drushrc.php file in the drush.php's directory. drush_load_config('drush'); // Load a drushrc.php file in the $ETC_PREFIX/etc/drush directory. drush_load_config('system'); // Load a drushrc.php file at ~/.drushrc.php. drush_load_config('user'); // Load a drushrc.php file in the ~/.drush directory. drush_load_config('home.drush'); // Load a custom config specified with the --config option. drush_load_config('custom'); // Process the site alias that specifies which instance // of drush (local or remote) this command will operate on. // We must do this after we load our config files (so that // site aliases are available), but before the rest // of the drush and drupal root bootstrap phases are // done, since site aliases may set option values that // affect these phases. drush_sitealias_check_arg(); // Load the config options from Drupal's sites/all/drush directory, even prior to bootstrapping the root drush_load_config('drupal'); // Similarly, load the Drupal site configuration options upfront. drush_load_config('site'); // If applicable swaps in shell alias value (or executes it). drush_shell_alias_replace(); // If drush_load_config defined a site alias that did not // exist before, then sitealias check arg might now match // against one of those aliases. if (drush_sitealias_check_arg() === TRUE) { $remote_host = drush_get_option('remote-host'); if (!isset($remote_host)) { // Load the config files for the "new" site. drush_load_config('drupal'); drush_load_config('site'); } } // Check to see if we 'use'd a site alias with site-set drush_sitealias_check_site_env(); _drush_bootstrap_global_options(); } function _drush_bootstrap_output_prepare() { $backend = drush_set_context('DRUSH_BACKEND', drush_get_option('backend')); // Pipe implies quiet. $quiet = drush_set_context('DRUSH_QUIET', drush_get_option(array('quiet', 'pipe'))); drush_set_context('DRUSH_PIPE', drush_get_option('pipe')); if ($backend) { // Load options passed as a JSON encoded string through STDIN. $stdin_options = _drush_backend_get_stdin(); if (is_array($stdin_options)) { drush_set_context('stdin', $stdin_options); } // Add an output buffer handler to collect output/pass through backend // packets. Using a chunksize of 2 ensures that each line is flushed // straight away. if ($quiet) { // Pass through of backend packets, discard regular output. ob_start('drush_backend_output_discard', 2); } else { // Collect output. ob_start('drush_backend_output_collect', 2); } } // In non-backend quiet mode we start buffering and discards it on command // completion. if ($quiet && !$backend) { ob_start(); } } /** * Determine which Drupal site will be selected. * * The Drupal site itself will be bootstrapped at a later * phase; at this time, we set context variables to * point to the drupal root, site URI and site configuration * path that will be used when needed. * * These early context variables are used to find * drush configuration and alias files stored with the * site to be bootstrapped. */ function _drush_bootstrap_select_drupal_site() { $drupal_root = drush_get_option('root'); if (!isset($drupal_root)) { $drupal_root = drush_locate_root(); } drush_set_context('DRUSH_SELECTED_DRUPAL_ROOT', $drupal_root); drush_set_context('DRUSH_SELECTED_DRUPAL_SITES_ALL_DRUSH', $drupal_root . '/sites/all/drush'); $uri = _drush_bootstrap_selected_uri(); drush_set_context('DRUSH_SELECTED_URI', $uri); drush_set_context('DRUSH_SELECTED_DRUPAL_SITE_CONF_PATH', drush_conf_path($uri)); if (!empty($drupal_root) && !empty($uri)) { // Create an alias '@self' _drush_sitealias_cache_alias('self', array('root' => $drupal_root, 'uri' => $uri)); } } /** * Sets up basic environment that controls where Drush looks for files on a * system-wide basis. Important to call for "early" functions that need to * work with unit tests. */ function _drush_bootstrap_base_environment() { // Copy ETC_PREFIX and SHARE_PREFIX from environment variables if available. // This alters where we check for server-wide config and alias files. // Used by unit test suite to provide a clean environment. if (getenv('ETC_PREFIX')) drush_set_context('ETC_PREFIX', getenv('ETC_PREFIX')); if (getenv('SHARE_PREFIX')) drush_set_context('SHARE_PREFIX', getenv('SHARE_PREFIX')); drush_set_context('DOC_PREFIX', DRUSH_BASE_PATH); if (!file_exists(DRUSH_BASE_PATH . '/README.md') && file_exists(drush_get_context('SHARE_PREFIX', '/usr') . '/share/doc/drush' . '/README.md')) { drush_set_context('DOC_PREFIX', drush_get_context('SHARE_PREFIX', '/usr') . '/share/doc/drush'); } $alias_path =& drush_get_context('ALIAS_PATH'); $default_prefix_configuration = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : ''; $default_prefix_commandfile = drush_is_windows() ? getenv('ALLUSERSPROFILE') . '/Drush' : '/usr'; $site_wide_configuration_dir = drush_get_context('ETC_PREFIX', $default_prefix_configuration) . '/etc/drush'; $site_wide_commandfile_dir = drush_get_context('SHARE_PREFIX', $default_prefix_commandfile) . '/share/drush/commands'; $alias_path[] = $site_wide_configuration_dir; drush_set_context('DRUSH_SITE_WIDE_CONFIGURATION', $site_wide_configuration_dir); drush_set_context('DRUSH_SITE_WIDE_COMMANDFILES', $site_wide_commandfile_dir); $alias_path[] = dirname(__FILE__) . '/..'; if(!is_null(drush_server_home())) { $alias_path[] = drush_server_home() . '/.drush'; drush_set_context('DRUSH_PER_USER_CONFIGURATION', drush_server_home() . '/.drush'); } } function _drush_bootstrap_global_options() { // Debug implies verbose drush_set_context('DRUSH_VERBOSE', drush_get_option(array('verbose', 'debug'), FALSE)); drush_set_context('DRUSH_DEBUG', drush_get_option('debug')); // Backend implies affirmative unless negative is explicitly specified drush_set_context('DRUSH_NEGATIVE', drush_get_option('no', FALSE)); drush_set_context('DRUSH_AFFIRMATIVE', drush_get_option(array('yes', 'pipe'), FALSE) || (drush_get_context('DRUSH_BACKEND') && !drush_get_context('DRUSH_NEGATIVE'))); drush_set_context('DRUSH_SIMULATE', drush_get_option('simulate', FALSE)); // Suppress colored logging if --nocolor option is explicitly given or if // terminal does not support it. $nocolor = (drush_get_option('nocolor', FALSE)); if (!$nocolor) { // Check for colorless terminal. If there is no terminal, then // 'tput colors 2>&1' will return "tput: No value for $TERM and no -T specified", // which is not numeric and therefore will put us in no-color mode. $colors = exec('tput colors 2>&1'); $nocolor = !($colors === FALSE || (is_numeric($colors) && $colors >= 3)); } drush_set_context('DRUSH_NOCOLOR', $nocolor); } /** * Validate the DRUSH_BOOTSTRAP_DRUPAL_ROOT phase. * * In this function, we will check if a valid Drupal directory is available. * We also determine the value that will be stored in the DRUSH_DRUPAL_ROOT * context and DRUPAL_ROOT constant if it is considered a valid option. */ function _drush_bootstrap_drupal_root_validate() { $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); if (empty($drupal_root)) { return drush_bootstrap_error('DRUSH_NO_DRUPAL_ROOT', dt("A Drupal installation directory could not be found")); } if (!$signature = drush_valid_drupal_root($drupal_root)) { 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))); } drush_bootstrap_value('drupal_root', $drupal_root); define('DRUSH_DRUPAL_SIGNATURE', $signature); return TRUE; } /** * Bootstrap Drush with a valid Drupal Directory. * * In this function, the pwd will be moved to the root * of the Drupal installation. * * The DRUSH_DRUPAL_ROOT context, DRUSH_DRUPAL_CORE context, DRUPAL_ROOT, and the * DRUSH_DRUPAL_CORE constants are populated from the value that we determined during * the validation phase. * * We also now load the drushrc.php for this specific platform. * We can now include files from the Drupal Tree, and figure * out more context about the platform, such as the version of Drupal. */ function _drush_bootstrap_drupal_root() { $drupal_root = drush_set_context('DRUSH_DRUPAL_ROOT', drush_bootstrap_value('drupal_root')); define('DRUPAL_ROOT', $drupal_root); chdir($drupal_root); // Beware the poison pill: prefer drush_drupal_version() and // drush_drupal_major_version() to 'DRUSH_DRUPAL_VERSION' and // 'DRUSH_DRUPAL_MAJOR_VERSION', respectively. See http://drupal.org/node/1816192 $version = drush_set_context('DRUSH_DRUPAL_VERSION', drush_drupal_version()); $major_version = drush_set_context('DRUSH_DRUPAL_MAJOR_VERSION', drush_drupal_major_version()); if (($major_version < 6) || ($major_version > 7)) { if ($major_version < 6) { $recommended_version = 4; } else { $recommended_version = 6; } drush_set_error('DRUSH_DRUPAL_VERSION_UNSUPPORTED', dt('Drush !drush_version does not support Drupal !major_version. Use Drush !recommended_version instead.', array('!drush_version' => DRUSH_VERSION, '!major_version' => $major_version, '!recommended_version' => $recommended_version))); } drush_set_context('DRUSH_DRUPAL_CORE', DRUPAL_ROOT); define('DRUSH_DRUPAL_CORE', DRUPAL_ROOT); _drush_bootstrap_global_options(); drush_log(dt("Initialized Drupal !version root directory at !drupal_root", array("!version" => $version, '!drupal_root' => $drupal_root))); } /** * Find the URI that has been selected by the --uri / -l option * or the cwd. */ function _drush_bootstrap_selected_uri() { $uri = drush_get_option('uri'); if (!isset($uri)) { $site_path = drush_site_path(); $elements = explode('/', $site_path); $current = array_pop($elements); if (!$current) { $current = 'default'; } $uri = 'http://'. $current; } return $uri; } /** * VALIDATE the DRUSH_BOOTSTRAP_DRUPAL_SITE phase. * * In this function we determine the URL used for the command, * and check for a valid settings.php file. * * To do this, we need to set up the $_SERVER environment variable, * to allow us to use conf_path to determine what Drupal will load * as a configuration file. */ function _drush_bootstrap_drupal_site_validate() { $drush_uri = drush_get_context('DRUSH_SELECTED_URI'); // Fake the necessary HTTP headers that Drupal needs: if ($drush_uri) { $drupal_base_url = parse_url($drush_uri); // If there's no url scheme set, add http:// and re-parse the url // so the host and path values are set accurately. if (!array_key_exists('scheme', $drupal_base_url)) { $drush_uri = 'http://' . $drush_uri; $drupal_base_url = parse_url($drush_uri); } // Fill in defaults. $drupal_base_url += array( 'path' => NULL, 'host' => NULL, 'port' => NULL, ); $_SERVER['HTTP_HOST'] = $drupal_base_url['host']; if ($drupal_base_url['scheme'] == 'https') { $_SERVER['HTTPS'] = 'on'; } if ($drupal_base_url['port']) { $_SERVER['HTTP_HOST'] .= ':' . $drupal_base_url['port']; } $_SERVER['SERVER_PORT'] = $drupal_base_url['port']; if (array_key_exists('path', $drupal_base_url)) { $_SERVER['PHP_SELF'] = $drupal_base_url['path'] . '/index.php'; } else { $_SERVER['PHP_SELF'] = '/index.php'; } } else { $_SERVER['HTTP_HOST'] = 'default'; $_SERVER['PHP_SELF'] = '/index.php'; } $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; $_SERVER['REQUEST_METHOD'] = NULL; $_SERVER['SERVER_SOFTWARE'] = NULL; $_SERVER['HTTP_USER_AGENT'] = NULL; $site = drush_bootstrap_value('site', $_SERVER['HTTP_HOST']); $conf_path = drush_bootstrap_value('conf_path', conf_path(TRUE, TRUE)); $conf_file = "./$conf_path/settings.php"; if (!file_exists($conf_file)) { return drush_bootstrap_error('DRUPAL_SITE_SETTINGS_NOT_FOUND', dt("Could not find a Drupal settings.php file at !file.", array('!file' => $conf_file))); } return TRUE; } /** * Called by _drush_bootstrap_drupal_site to do the main work * of the drush drupal site bootstrap. */ function _drush_bootstrap_do_drupal_site() { $drush_uri = drush_get_context('DRUSH_SELECTED_URI'); drush_set_context('DRUSH_URI', $drush_uri); $site = drush_set_context('DRUSH_DRUPAL_SITE', drush_bootstrap_value('site')); $conf_path = drush_set_context('DRUSH_DRUPAL_SITE_ROOT', drush_bootstrap_value('conf_path')); drush_log(dt("Initialized Drupal site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path))); _drush_bootstrap_global_options(); } /** * Initialize a site on the Drupal root. * * We now set various contexts that we determined and confirmed to be valid. * Additionally we load an optional drushrc.php file in the site directory. */ function _drush_bootstrap_drupal_site() { _drush_bootstrap_do_drupal_site(); } /** * Initialize and load the Drupal configuration files. * * We process and store a normalized set of database credentials * from the loaded configuration file, so we can validate them * and access them easily in the future. * * Also override Drupal variables as per --variables option. */ function _drush_bootstrap_drupal_configuration() { global $conf; drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); // Unset drupal error handler and restore drush's one. if (drush_drupal_major_version() >= 7) { restore_error_handler(); } // Force Drupal6 not to store queries since we are not outputting them. // Don't run poormanscron during Drush request (D7+). $override = array( 'dev_query' => FALSE, 'cron_safe_threshold' => 0, ); $current_override = drush_get_option_list('variables'); foreach ($current_override as $name => $value) { if (is_numeric($name) && (strpos($value, '=') !== FALSE)) { list($name, $value) = explode('=', $value, 2); } $override[$name] = $value; } $conf = is_array($conf) ? array_merge($conf, $override) : $conf; // Populate the DRUSH_DB_CREDENTIALS with the fields loaded from the configuration. $creds = array(); switch (drush_drupal_major_version()) { case 6: if (!empty($GLOBALS['db_url'])) { $url = $GLOBALS['db_url']; if (is_array($url)) { $url = $url['default']; } $parts = parse_url($url); $parts += array('pass' => '', 'port' => ''); $creds['driver'] = $parts['scheme']; $creds['user'] = urldecode($parts['user']); $creds['host'] = $parts['host']; $creds['port'] = $parts['port']; $creds['pass'] = urldecode($parts['pass']); $creds['name'] = trim($parts['path'], '/'); } break; case 7: default: if (!empty($GLOBALS['databases']['default']['default'])) { $conn = $GLOBALS['databases']['default']['default']; // Fill in defaults to prevent notices. $conn += array( 'username' => NULL, 'host' => NULL, 'port' => NULL, 'password' => NULL, 'database' => NULL, 'unix_socket' => NULL, ); $creds['driver'] = $conn['driver']; $creds['user'] = $conn['username']; $creds['unix_socket'] = $conn['unix_socket']; $creds['host'] = $conn['host']; $creds['port'] = $conn['port']; $creds['name'] = $conn['database']; $creds['pass'] = $conn['password']; } break; } drush_set_context('DRUSH_DB_CREDENTIALS', $creds); } /** * Validate the DRUSH_BOOTSTRAP_DRUPAL_DATABASE phase * * Attempt to making a working database connection using the * database credentials that were loaded during the previous * phase. */ function _drush_bootstrap_drupal_database_validate() { if (!drush_valid_db_credentials()) { return drush_bootstrap_error('DRUSH_DRUPAL_DB_ERROR'); } return TRUE; } /** * Boostrap the Drupal database. */ function _drush_bootstrap_drupal_database() { drush_log(dt("Successfully connected to the Drupal database."), 'bootstrap'); drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); } /** * Attempt to load the full Drupal system. */ function _drush_bootstrap_drupal_full() { if (!drush_get_context('DRUSH_QUIET', FALSE)) { ob_start(); } drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); if (!drush_get_context('DRUSH_QUIET', FALSE)) { ob_end_clean(); } // Unset drupal error handler and restore drush's one. if (drush_drupal_major_version() == 6) { restore_error_handler(); } // If needed, prod module_implements() to recognize our system_watchdog() implementation. $dogs = module_implements('watchdog'); if (!in_array('system', $dogs)) { // Note that this resets module_implements cache. module_implements('watchdog', FALSE, TRUE); } _drush_log_drupal_messages(); } /** * Log into the bootstrapped Drupal site with a specific * username or user id. */ function _drush_bootstrap_drupal_login() { $drush_user = drush_set_context('DRUSH_USER', drush_get_option('user', 0)); drush_drupal_login($drush_user); _drush_log_drupal_messages(); } /** * Helper function to store any context settings that are being validated. */ function drush_bootstrap_value($context, $value = null) { $values =& drush_get_context('DRUSH_BOOTSTRAP_VALUES', array()); if (!is_null($value)) { $values[$context] = $value; } if (array_key_exists($context, $values)) { return $values[$context]; } return null; } /** * Prepare drush for bootstrap * * All pre-flight checks and includes necessary to begin the bootstrap * process. * * Ran before drush_main(). * * @see drush_main() * @see drush.php */ function drush_bootstrap_prepare() { define('DRUSH_BASE_PATH', dirname(dirname(__FILE__))); require_once DRUSH_BASE_PATH . '/includes/environment.inc'; require_once DRUSH_BASE_PATH . '/includes/command.inc'; require_once DRUSH_BASE_PATH . '/includes/drush.inc'; require_once DRUSH_BASE_PATH . '/includes/backend.inc'; require_once DRUSH_BASE_PATH . '/includes/batch.inc'; require_once DRUSH_BASE_PATH . '/includes/context.inc'; require_once DRUSH_BASE_PATH . '/includes/sitealias.inc'; require_once DRUSH_BASE_PATH . '/includes/exec.inc'; require_once DRUSH_BASE_PATH . '/includes/drupal.inc'; require_once DRUSH_BASE_PATH . '/includes/output.inc'; require_once DRUSH_BASE_PATH . '/includes/cache.inc'; require_once DRUSH_BASE_PATH . '/includes/filesystem.inc'; require_once DRUSH_BASE_PATH . '/includes/dbtng.inc'; // Terminate immediately unless invoked as a command line script if (!drush_verify_cli()) { return drush_set_error('DRUSH_REQUIREMENTS_ERROR', dt('Drush is designed to run via the command line.')); } // Check supported version of PHP. define('DRUSH_MINIMUM_PHP', '5.2.0'); if (version_compare(phpversion(), DRUSH_MINIMUM_PHP) < 0) { return drush_set_error('DRUSH_REQUIREMENTS_ERROR', dt('Your command line PHP installation is too old. Drush requires at least PHP !version.', array('!version' => DRUSH_MINIMUM_PHP))); } $drush_info = drush_read_drush_info(); define('DRUSH_VERSION', $drush_info['drush_version']); $version_parts = explode('.', DRUSH_VERSION); define('DRUSH_MAJOR_VERSION', $version_parts[0]); define('DRUSH_MINOR_VERSION', $version_parts[1]); define('DRUSH_REQUEST_TIME', microtime(TRUE)); drush_set_context('argc', $GLOBALS['argc']); drush_set_context('argv', $GLOBALS['argv']); // Set an error handler and a shutdown function set_error_handler('drush_error_handler'); register_shutdown_function('drush_shutdown'); drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE); // We need some global options/arguments processed at this early stage. drush_parse_args(); } /** * Cleanup our bootstrap. * * This is ran after we have bootstrapped and dispatched properly in * drush_main(). * * @see drush_main() */ function drush_bootstrap_finish() { // We set this context to let the shutdown function know we reached the end of drush_main(); drush_set_context("DRUSH_EXECUTION_COMPLETED", TRUE); } /** * Shutdown function for use while Drupal is bootstrapping and to return any * registered errors. * * The shutdown command checks whether certain options are set to reliably * detect and log some common Drupal initialization errors. * * If the command is being executed with the --backend option, the script * will return a json string containing the options and log information * used by the script. * * The command will exit with '1' if it was successfully executed, and the * result of drush_get_error() if it wasn't. */ function drush_shutdown() { // Mysteriously make $user available during sess_write(). Avoids a NOTICE. global $user; if (!drush_get_context('DRUSH_EXECUTION_COMPLETED', FALSE) && !drush_get_context('DRUSH_USER_ABORT', FALSE)) { $php_error_message = ''; if ($error = error_get_last()) { $php_error_message = "\n" . dt('Error: !message in !file, line !line', array('!message' => $error['message'], '!file' => $error['file'], '!line' => $error['line'])); } // We did not reach the end of the drush_main function, // this generally means somewhere in the code a call to exit(), // was made. We catch this, so that we can trigger an error in // those cases. drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command terminated abnormally due to an unrecoverable error.!message", array('!message' => $php_error_message))); // Attempt to give the user some advice about how to fix the problem _drush_postmortem(); } $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); if (drush_get_context('DRUSH_BOOTSTRAPPING')) { switch ($phase) { case DRUSH_BOOTSTRAP_DRUPAL_FULL : ob_end_clean(); _drush_log_drupal_messages(); drush_set_error('DRUSH_DRUPAL_BOOTSTRAP_ERROR'); break; } } if (drush_get_context('DRUSH_BACKEND', FALSE)) { drush_backend_output(); } elseif (drush_get_context('DRUSH_QUIET', FALSE)) { ob_end_clean(); // If we are in pipe mode, emit the compact representation of the command, if available. if (drush_get_context('DRUSH_PIPE')) { drush_pipe_output(); } } /** * For now, drush skips end of page processing on D7. Doing so could write * cache entries to module_implements and lookup_cache that don't match web requests. */ // if (drush_drupal_major_version() >= 7 && function_exists('drupal_page_footer')) { // drupal_page_footer(); // } // this way drush_return_status will always be the last shutdown function (unless other shutdown functions register shutdown functions...) // and won't prevent other registered shutdown functions (IE from numerous cron methods) from running by calling exit() before they get a chance. register_shutdown_function('drush_return_status'); } function drush_return_status() { exit((drush_get_error()) ? DRUSH_FRAMEWORK_ERROR : DRUSH_SUCCESS); } drush-5.10.0/includes/cache.inc000066400000000000000000000310301222105546100162610ustar00rootroot00000000000000get($cid); $mess = $ret ? "HIT" : "MISS"; drush_log(dt("Cache !mess cid: !cid", array('!mess' => $mess, '!cid' => $cid)), 'debug'); return _drush_cache_get_object($bin)->get($cid); } /** * Return data from the persistent cache when given an array of cache IDs. * * @param $cids * An array of cache IDs for the data to retrieve. This is passed by * reference, and will have the IDs successfully returned from cache removed. * @param $bin * The cache bin where the data is stored. * @return * An array of the items successfully returned from cache indexed by cid. */ function drush_cache_get_multiple(array &$cids, $bin = 'default') { return _drush_cache_get_object($bin)->getMultiple($cids); } /** * Store data in the persistent cache. * * @param $cid * The cache ID of the data to store. * @param $data * The data to store in the cache. * @param $bin * The cache bin to store the data in. * @param $expire * One of the following values: * - DRUSH_CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using cache_clear_all() with a cache ID. * - DRUSH_CACHE_TEMPORARY: Indicates that the item should be removed at the next * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like DRUSH_CACHE_TEMPORARY. */ function drush_cache_set($cid, $data, $bin = 'default', $expire = DRUSH_CACHE_PERMANENT) { $ret = _drush_cache_get_object($bin)->set($cid, $data, $expire); if ($ret) drush_log(dt("Cache SET cid: !cid", array('!cid' => $cid)), 'debug'); return $ret; } /** * Expire data from the cache. * * If called without arguments, expirable entries will be cleared from all known * cache bins. * * @param $cid * If set, the cache ID to delete. Otherwise, all cache entries that can * expire are deleted. * * @param $bin * If set, the bin $bin to delete from. Mandatory * argument if $cid is set. * * @param $wildcard * If $wildcard is TRUE, cache IDs starting with $cid are deleted in * addition to the exact cache ID specified by $cid. If $wildcard is * TRUE and $cid is '*' then the entire bin $bin is emptied. */ function drush_cache_clear_all($cid = NULL, $bin = 'default', $wildcard = FALSE) { if (!isset($cid) && !isset($bin)) { foreach (drush_cache_get_bins() as $bin) { _drush_cache_get_object($bin)->clear(); } return; } return _drush_cache_get_object($bin)->clear($cid, $wildcard); } /** * Check if a cache bin is empty. * * A cache bin is considered empty if it does not contain any valid data for any * cache ID. * * @param $bin * The cache bin to check. * @return * TRUE if the cache bin specified is empty. */ function _drush_cache_is_empty($bin) { return _drush_cache_get_object($bin)->isEmpty(); } /** * Return drush cache bins and any bins added by * implementing hook_drush_flush_caches(). */ function drush_cache_get_bins() { $drush = array('default'); return array_merge(drush_command_invoke_all('drush_flush_caches'), $drush); } /** * Create a cache id from a given prefix, contexts, and any additional * parameters necessary. * * @param prefix * A human readable cid prefix that will not be hashed. * @param contexts * Optional. An array of drush contexts that will be used to build a unique hash. * @param params * Optional. An array of any addition parameters to be hashed. * * @return * A cache id string. */ function drush_get_cid($prefix, $contexts = array(), $params = array()) { $cid = array(); foreach ($contexts as $context) { $c = drush_get_context($context); if (!empty($c)) { $cid[] = is_scalar($c) ? $c : serialize($c); } } foreach ($params as $param) { $cid[] = $param; } return DRUSH_VERSION . '-' . $prefix . '-' . md5(implode("", $cid)); } /** * Interface for cache implementations. * * All cache implementations have to implement this interface. * DrushDatabaseCache provides the default implementation, which can be * consulted as an example. * * To make Drush use your implementation for a certain cache bin, you have to * set a variable with the name of the cache bin as its key and the name of * your class as its value. For example, if your implementation of * DrushCacheInterface was called MyCustomCache, the following line in * drushrc.php would make Drush use it for the 'example' bin: * @code * $options['cache-class-example'] = 'MyCustomCache; * @endcode * * Additionally, you can register your cache implementation to be used by * default for all cache bins by setting the option 'cache-default-class' to * the name of your implementation of the DrushCacheInterface, e.g. * @code * $options['cache-default-class'] = 'MyCustomCache; * @endcode * * @see _drush_cache_get_object() * @see DrupalCacheInterface */ interface DrushCacheInterface { /** * Constructor. * * @param $bin * The cache bin for which the object is created. */ function __construct($bin); /** * Return data from the persistent cache. * * @param $cid * The cache ID of the data to retrieve. * @return * The cache or FALSE on failure. */ function get($cid); /** * Return data from the persistent cache when given an array of cache IDs. * * @param $cids * An array of cache IDs for the data to retrieve. This is passed by * reference, and will have the IDs successfully returned from cache * removed. * @return * An array of the items successfully returned from cache indexed by cid. */ function getMultiple(&$cids); /** * Store data in the persistent cache. * * @param $cid * The cache ID of the data to store. * @param $data * The data to store in the cache. * @param $expire * One of the following values: * - DRUSH_CACHE_PERMANENT: Indicates that the item should never be removed unless * explicitly told to using _drush_cache_clear_all() with a cache ID. * - DRUSH_CACHE_TEMPORARY: Indicates that the item should be removed at the next * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. */ function set($cid, $data, $expire = DRUSH_CACHE_PERMANENT); /** * Expire data from the cache. If called without arguments, expirable * entries will be cleared from all known cache bins. * * @param $cid * If set, the cache ID to delete. Otherwise, all cache entries that can * expire are deleted. * @param $wildcard * If set to TRUE, the $cid is treated as a substring * to match rather than a complete ID. The match is a right hand * match. If '*' is given as $cid, the bin $bin will be emptied. */ function clear($cid = NULL, $wildcard = FALSE); /** * Check if a cache bin is empty. * * A cache bin is considered empty if it does not contain any valid data for * any cache ID. * * @return * TRUE if the cache bin specified is empty. */ function isEmpty(); } /** * Default cache implementation. * * This is Drush's default cache implementation. It uses plain text files * containing serialized php to store cached data. Each cache bin corresponds * to a directory by the same name. */ class DrushFileCache implements DrushCacheInterface { const EXTENSION = '.cache'; protected $bin; function __construct($bin) { $this->bin = $bin; $this->directory = $this->cacheDirectory(); } function cacheDirectory($bin = NULL) { $bin = $bin ? $bin : $this->bin; return drush_directory_cache($bin); } function get($cid) { $cids = array($cid); $cache = $this->getMultiple($cids); return reset($cache); } function getMultiple(&$cids) { try { $cache = array(); foreach ($cids as $cid) { $filename = $this->getFilePath($cid); if (!file_exists($filename)) throw new Exception; $item = $this->readFile($filename); if ($item) { $cache[$cid] = $item; } } $cids = array_diff($cids, array_keys($cache)); return $cache; } catch (Exception $e) { return array(); } } function readFile($filename) { $item = file_get_contents($filename); return $item ? unserialize($item) : FALSE; } function set($cid, $data, $expire = DRUSH_CACHE_PERMANENT, array $headers = NULL) { $created = time(); $cache = new stdClass; $cache->cid = $cid; $cache->data = is_object($data) ? clone $data : $data; $cache->created = $created; $cache->headers = $headers; if ($expire == DRUSH_CACHE_TEMPORARY) { $cache->expire = $created + 2591999; } // Expire time is in seconds if less than 30 days, otherwise is a timestamp. elseif ($expire != DRUSH_CACHE_PERMANENT && $expire < 2592000) { $cache->expire = $created + $expire; } else { $cache->expire = $expire; } // Ensure the cache directory still exists, in case a backend process // cleared the cache after the cache was initialized. drush_mkdir($this->directory); $filename = $this->getFilePath($cid); return $this->writeFile($filename, $cache); } function writeFile($filename, $cache) { return file_put_contents($filename, serialize($cache)); } function clear($cid = NULL, $wildcard = FALSE) { $bin_dir = $this->cacheDirectory(); $files = array(); if (empty($cid)) { drush_delete_dir($bin_dir, TRUE); } else { if ($wildcard) { if ($cid == '*') { drush_delete_dir($bin_dir, TRUE); } else { $matches = drush_scan_directory($bin_dir, "/^$cid/", array('.', '..')); $files = $files + array_keys($matches); } } else { $files[] = $this->getFilePath($cid); } foreach ($files as $f) { // Delete immediately instead of drush_register_file_for_deletion(). unlink($f); } } } function isEmpty() { $files = drush_scan_directory($dir, "//", array('.', '..')); return empty($files); } /** * Converts a cache id to a full path. * * @param $cid * The cache ID of the data to retrieve. * @return * The full path to the cache file. */ protected function getFilePath($cid) { return $this->directory . '/' . str_replace(array(':'), '.', $cid) . self::EXTENSION; } } /** * JSON cache storage backend. */ class DrushJSONCache extends DrushFileCache { const EXTENSION = '.json'; function readFile($filename) { $item = file_get_contents($filename); return $item ? (object)drush_json_decode($item) : FALSE; } function writeFile($filename, $cache) { return file_put_contents($filename, drush_json_encode($cache)); } } drush-5.10.0/includes/command.inc000066400000000000000000002112001222105546100166330ustar00rootroot00000000000000 $command))); } } /** * Invoke a command in a new process, targeting the site specified by * the provided site alias record. * * Use this function instead of drush_backend_invoke_sitealias, * drush_backend_invoke_args, or drush_backend_invoke_command * (all obsolete in drush 5). * * @param array $site_alias_record * The site record to execute the command on. Use '@self' to run on the current site. * @param string $command_name * The command to invoke. * @param array $commandline_args * The arguments to pass to the command. * @param array $commandline_options * The options (e.g. --select) to provide to the command. * @param $backend_options * TRUE - integrate errors * FALSE - do not integrate errors * array - @see drush_backend_invoke_concurrent * There are also several options that _only_ work when set in * this parameter. They include: * 'invoke-multiple' * If $site_alias_record represents a single site, then 'invoke-multiple' * will cause the _same_ command with the _same_ arguments and options * to be invoked concurrently (e.g. for running concurrent batch processes). * 'concurrency' * Limits the number of concurrent processes that will run at the same time. * Defaults to '4'. * 'override-simulated' * Forces the command to run, even in 'simulated' mode. Useful for * commands that do not change any state on the machine, e.g. to fetch * database information for sql-sync via sql-conf. * 'interactive' * Overrides the backend invoke process to run commands interactively. * 'fork' * Overrides the backend invoke process to run non blocking commands in * the background. Forks a new process by adding a '&' at the end of the * command. The calling process does not receive any output from the child * process. The fork option is used to spawn a process that outlives its * parent. * * @return * If the command could not be completed successfully, FALSE. * If the command was completed, this will return an associative * array containing the results of the API call. * @see drush_backend_get_result() * * Do not change the signature of this function! drush_invoke_process * is one of the key Drush APIs. See http://drupal.org/node/1152908 */ function drush_invoke_process($site_alias_record, $command_name, $commandline_args = array(), $commandline_options = array(), $backend_options = TRUE) { if (is_array($site_alias_record) && array_key_exists('site-list', $site_alias_record)) { $site_alias_records = drush_sitealias_resolve_sitespecs($site_alias_record['site-list']); $site_alias_records = drush_sitealias_simplify_names($site_alias_records); foreach ($site_alias_records as $alias_name => $alias_record) { $invocations[] = array( 'site' => $alias_record, 'command' => $command_name, 'args' => $commandline_args, ); } } else { $invocations[] = array( 'site' => $site_alias_record, 'command' => $command_name, 'args' => $commandline_args); $invoke_multiple = drush_get_option_override($backend_options, 'invoke-multiple', 0); if ($invoke_multiple) { $invocations = array_fill(0, $invoke_multiple, $invocations[0]); } } return drush_backend_invoke_concurrent($invocations, $commandline_options, $backend_options); } /** * Given a command record, dispatch it as if it were * the original command. Executes in the currently * bootstrapped site using the current option contexts. * Note that drush_dispatch will not bootstrap any further than the * current command has already bootstrapped; therefore, you should only invoke * commands that have the same (or lower) bootstrap requirements. * * @param command * A full $command such as returned by drush_get_commands(), * or a string containing the name of the command record from * drush_get_commands() to call. * @param arguments * An array of argument values. * * @see drush_topic_docs_topic(). */ function drush_dispatch($command, $arguments = array()) { drush_set_command($command); $return = FALSE; if ($command) { // Add arguments, if this has not already been done. // (If the command was fetched from drush_parse_command, // then you cannot provide arguments to drush_dispatch.) if (empty($command['arguments'])) { _drush_prepare_command($command, $arguments); } // Add command-specific options, if applicable. drush_command_default_options($command); // Test to see if any of the options in the 'cli' context // are not represented in the command structure. if ((_drush_verify_cli_options($command) === FALSE) || (_drush_verify_cli_arguments($command) === FALSE)) { return FALSE; } // Include and validate command engines. if (_drush_load_command_engines($command) === FALSE) { return FALSE; } // Call the callback function of the active command. $return = call_user_func_array($command['callback'], $command['arguments']); } // Add a final log entry, just so a timestamp appears. drush_log(dt('Command dispatch complete'), 'notice'); return $return; } /** * Entry point for commands into the drush_invoke() API * * If a command does not have a callback specified, this function will be called. * * This function will trigger $hook_drush_init, then if no errors occur, * it will call drush_invoke() with the command that was dispatch. * * If no errors have occured, it will run $hook_drush_exit. */ function drush_command() { $args = func_get_args(); $command = drush_get_command(); foreach (drush_command_implements("drush_init") as $name) { $func = $name . '_drush_init'; if (drush_get_option('show-invoke')) { drush_log(dt("Calling global init hook: !func", array('!name' => $name, '!func' => $func . '()')), 'bootstrap'); } call_user_func_array($func, $args); _drush_log_drupal_messages(); } if (!drush_get_error()) { _drush_invoke_hooks($command['command-hook'], $args, $command['commandfile']); } if (!drush_get_error()) { foreach (drush_command_implements('drush_exit') as $name) { $func = $name . '_drush_exit'; if (drush_get_option('show-invoke')) { drush_log(dt("Calling global exit hook: !func", array('!name' => $name, '!func' => $func . '()')), 'bootstrap'); } call_user_func_array($func, $args); _drush_log_drupal_messages(); } } } /** * Invoke Drush API calls, including all hooks. * * This is an internal function; it is called from drush_dispatch via * drush_command, but only if the command does not specify a 'callback' * function. If a callback function is specified, it will be called * instead of drush_command + _drush_invoke_hooks. * * Executes the specified command with the specified arguments on the * currently bootstrapped site using the current option contexts. * Note that _drush_invoke_hooks will not bootstrap any further than the * current command has already bootstrapped; therefore, you should only invoke * commands that have the same (or lower) bootstrap requirements. * * Call the correct hook for all the modules that implement it. * Additionally, the ability to rollback when an error has been encountered is also provided. * If at any point during execution, the drush_get_error() function returns anything but 0, * drush_invoke() will trigger $hook_rollback for each of the hooks that implement it, * in reverse order from how they were executed. Rollbacks are also triggered any * time a hook function returns FALSE. * * This function will also trigger pre_$hook and post_$hook variants of the hook * and its rollbacks automatically. * * HOW DRUSH HOOK FUNCTIONS ARE NAMED: * * The name of the hook is composed from the name of the command and the name of * the command file that the command definition is declared in. The general * form for the hook filename is: * * drush_COMMANDFILE_COMMANDNAME * * In many cases, drush commands that are functionally part of a common collection * of similar commands will all be declared in the same file, and every command * defined in that file will start with the same command prefix. For example, the * command file "pm.drush.inc" defines commands such as "pm-enable" and "pm-disable". * In the case of "pm-enable", the command file is "pm", and and command name is * "pm-enable". When the command name starts with the same sequence of characters * as the command file, then the repeated sequence is dropped; thus, the command * hook for "pm-enable" is "drush_pm_enable", not "drush_pm_pm_enable". * * @param command * The drush command to execute. * @param args * An array of arguments to the command OR a single non-array argument. * @param defined_in_commandfile * The name of the commandfile that this command exists in. Will be looked up * if not provided. * @return * The return value will be passed along to the caller if --backend option is * present. A boolean FALSE indicates failure and rollback will be intitated. * * This function should not be called directly. * @see drush_invoke() and @see drush_invoke_process() */ function _drush_invoke_hooks($command, $args, $defined_in_commandfile = NULL) { if ($defined_in_commandfile == NULL) { $defined_in_commandfile = drush_get_commandfile_for_command($command); } // If someone passed a standalone arg, convert it to a single-element array if (!is_array($args)) { $args = array($args); } // Include the external command file used by this command, if there is one. drush_command_include($command); // Generate the base name for the hook by converting all // dashes in the command name to underscores. $hook = str_replace("-", "_", $command); // Call the hook init function, if it exists. // If a command needs to bootstrap, it is advisable // to do so in _init; otherwise, new commandfiles // will miss out on participating in any stage that // has passed or started at the time it was discovered. $func = 'drush_' . $hook . '_init'; if (function_exists($func)) { drush_log(dt("Calling drush command init function: !func", array('!func' => $func)), 'bootstrap'); call_user_func_array($func, $args); _drush_log_drupal_messages(); if (drush_get_error()) { drush_log(dt('The command @command could not be initialized.', array('@command' => $command)), 'error'); return FALSE; } } $rollback = FALSE; $completed = array(); $available_rollbacks = array(); $all_available_hooks = array(); // Iterate through the different hook variations $variations = array($hook . "_pre_validate", $hook . "_validate", "pre_$hook", $hook, "post_$hook"); foreach ($variations as $var_hook) { // Get the list of command files. // We re-fetch the list every time through // the loop in case one of the hook function // does something that will add additional // commandfiles to the list (i.e. bootstrapping // to a higher phase will do this). $list = drush_commandfile_list(); // Run all of the functions available for this variation foreach ($list as $commandfile => $filename) { $func = sprintf("drush_%s_%s", $commandfile, $var_hook); if (($defined_in_commandfile == $commandfile) && ($commandfile . "_" == substr($var_hook . "_",0,strlen($commandfile)+ 1))) { $func = sprintf("drush_%s", $var_hook); } if (function_exists($func)) { $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']'; $available_rollbacks[] = $func . '_rollback'; $completed[] = $func; $result = call_user_func_array($func, $args); // Only the 'main' callback can send data to backend. if ($var_hook == $hook) { // If the hook already called drush_backend_set_result, // then return that value. If it did not, then the return // value from the hook will be the value returned from // this routine. $return = drush_backend_get_result(); if (empty($return)) { drush_backend_set_result($result); $return = $result; } } _drush_log_drupal_messages(); if (drush_get_error() || ($result === FALSE)) { $rollback = TRUE; // break out of the foreach variations and foreach list break 2; } } else { $all_available_hooks[] = $func; } } } // If no hook functions were found, print a warning. if (empty($completed)) { $default_command_hook = sprintf("drush_%s_%s", $defined_in_commandfile, $hook); if (($defined_in_commandfile . "_" == substr($hook . "_",0,strlen($defined_in_commandfile)+ 1))) { $default_command_hook = sprintf("drush_%s", $hook); } $dt_args = array( '!command' => $command, '!default_func' => $default_command_hook, ); $message = "No hook functions were found for !command. The primary hook function is !default_func(). Please implement this function. Run with --show-invoke to see all available hooks."; $return = drush_set_error('DRUSH_FUNCTION_NOT_FOUND', dt($message, $dt_args)); } if (drush_get_option('show-invoke')) { // We show all available hooks up to and including the one that failed (or all, if there were no failures) drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'ok'); } if (drush_get_option('show-invoke') && !empty($available_rollbacks)) { drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command, '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'ok'); } // Something went wrong, we need to undo. if ($rollback) { if (drush_get_option('confirm-rollback', FALSE)) { // Optionally ask for confirmation, --yes and --no are ignored from here on as we are about to finish this process. drush_set_context('DRUSH_AFFIRMATIVE', FALSE); drush_set_context('DRUSH_NEGATIVE', FALSE); $rollback = drush_confirm(dt('Do you want to rollback? (manual cleanup might be required otherwise)')); } if ($rollback) { foreach (array_reverse($completed) as $func) { $rb_func = $func . '_rollback'; if (function_exists($rb_func)) { call_user_func_array($rb_func, $args); _drush_log_drupal_messages(); drush_log(dt("Changes made in !func have been rolled back.", array('!func' => $func)), 'rollback'); } } } $return = FALSE; } if (isset($return)) { return $return; } } /** * Include, instantiate and validate command engines. * * @return FALSE if a engine doesn't validate. */ function _drush_load_command_engines($command) { foreach ($command['engines'] as $engine_type => $config) { drush_log(dt("Loading !engine engine.", array('!engine' => $engine_type), 'bootstrap')); $engine_info = drush_get_engines($engine_type); $engines = array_keys($engine_info['engines']); $default = isset($config['default'])?$config['default']:current($engines); // If the engine provides a command line option to choose between engine // implementations, get the user selection, if any. if (!empty($config['option'])) { $engine = drush_get_option($config['option'], $default); } // Otherwise the default engine is the only option. else { $engine = $default; } if (!in_array($engine, $engines)) { return drush_set_error('DRUSH_UNKNOWN_ENGINE_TYPE', dt('Unknown !engine_type engine !engine', array('!engine' => $engine, '!engine_type' => $engine_type))); } $result = drush_include_engine($engine_type, $engine); if ($result === FALSE) { return FALSE; } elseif (is_object($result) && method_exists($result, 'validate')) { $result = $result->validate(); } else { $function = strtr($engine_type, '-', '_') . '_validate'; if (function_exists($function)) { $result = call_user_func($function); } } if (!$result) { return FALSE; } } } /** * Add command structure info from each engine back into the command. */ function _drush_merge_engine_data(&$command) { foreach ($command['engines'] as $engine_type => $config) { // Normalize engines structure. if (!is_array($config)) { unset($command['engines'][$engine_type]); $command['engines'][$config] = array(); $engine_type = $config; } // Get all implementations for this engine type. $engine_info = drush_get_engines($engine_type); if ($engine_info === FALSE) { return FALSE; } // Override engine_info with customizations in the command. $config = $command['engines'][$engine_type] += $engine_info['info']; // Add engine type global options to the command. $command['options'] += $config['options']; $engine_data = array(); // If there's a single implementation for this engine type, it will be // loaded by default, and makes no sense to provide a command line option // to select the only flavor (ie. --release_info=updatexml), so we won't // add an option in this case. // Additionally, depending on the command, it may be convenient to extend // the command with the engine options. if (count($engine_info['engines']) == 1) { if ($config['add-options-to-command'] !== FALSE) { $engine = key($engine_info['engines']); $data = $engine_info['engines'][$engine]; foreach (array('options', 'sub-options') as $key) { if (isset($data[$key])) { $engine_data[$key] = $data[$key]; } } } } // Otherwise, provide a command option to choose between engines and add // the engine options and sub-obtions. else { // Process engines in order. First the default engine, the rest alphabetically. $default = $config['default']; $engines = array_keys($engine_info['engines']); asort($engines); array_unshift($engines, $default); $engines = array_unique($engines); // Extend the default engine description. $desc = $engine_info['engines'][$default]['description']; $engine_info['engines'][$default]['description'] = dt('Default type engine.', array('type' => $engine_type)) . ' ' . $desc; $engine_data += array( 'options' => array(), 'sub-options' => array(), ); foreach ($engines as $engine) { $data = $engine_info['engines'][$engine]; $option = $config['option'] . '=' . $engine; $engine_data['options'][$option] = array_key_exists('description', $data) ? $data['description'] : NULL; if (isset($data['options'])) { $engine_data['sub-options'][$option] = $data['options']; } if (isset($data['sub-options'])) { $engine_data['sub-options'] += $data['sub-options']; } } } $command = array_merge_recursive($command, $engine_data); } } /** * Fail with an error if the user specified options on the * command line that are not documented in the current command * record. */ function _drush_verify_cli_options($command) { // Start out with just the options in the current command record. $options = _drush_get_command_options($command); // Skip all tests if the command is marked to allow anything. // Also skip backend commands, which may have options on the commandline // that were inherited from the calling command. if (($command['allow-additional-options'] === TRUE) || (drush_get_option(array('backend', 'invoke'), FALSE))) { return TRUE; } // If 'allow-additional-options' contains a list of command names, // then union together all of the options from all of the commands. if (is_array($command['allow-additional-options'])) { $implemented = drush_get_commands(); foreach ($command['allow-additional-options'] as $subcommand_name) { if (array_key_exists($subcommand_name, $implemented)) { $options = array_merge($options, _drush_get_command_options($implemented[$subcommand_name])); } } } // Also add in global options $options = array_merge($options, drush_get_global_options()); // Now we will figure out which options in the cli context // are not represented in our options list. $cli_options = array_keys(drush_get_context('cli')); $allowed_options = _drush_flatten_options($options); $allowed_options = drush_append_negation_options($allowed_options); $disallowed_options = array_diff($cli_options, $allowed_options); if (!empty($disallowed_options)) { $unknown = count($disallowed_options) > 1 ? dt('Unknown options') : dt('Unknown option'); $msg = dt("@unknown: --@options. See `drush help @command` for available options. To suppress this error, add the option --strict=0.", array('@unknown' => $unknown, '@options' => implode(', --', $disallowed_options), '@command' => $command['command'])); if (drush_get_option('strict', TRUE)) { return drush_set_error('DRUSH_UNKNOWN_OPTION', $msg); } } // Next check to see if all required options were specified. $missing_required_options = array(); foreach ($command['options'] as $key => $value) { if (is_array($value) && array_key_exists('required', $value)) { $option_value = drush_get_option($key, NULL); if (!isset($option_value)) { $missing_required_options[] = $key; } } } if (!empty($missing_required_options)) { $missing = count($missing_required_options) > 1 ? dt('Missing required options') : dt('Missing required option'); return drush_set_error(dt("@missing: --@options. See `drush help @command` for information on usage.", array('@missing' => $missing, '@options' => implode(', --', $missing_required_options), '@command' => $command['command']))); } return TRUE; } function drush_append_negation_options($allowed_options) { $new_allowed = $allowed_options; foreach ($allowed_options as $option) { $new_allowed[] = 'no-' . $option; } return $new_allowed; } function _drush_verify_cli_arguments($command) { // Check to see if all of the required arguments // are specified. if ($command['required-arguments']) { $required_arg_count = $command['required-arguments']; if ($required_arg_count === TRUE) { $required_arg_count = count($command['argument-description']); } if ((count($command['arguments'])) < $required_arg_count) { $missing = count($required_arg_count) > 1 ? dt('Missing required arguments') : dt('Missing required argument'); $required = implode("', '", array_keys($command['argument-description'])); return drush_set_error(dt("@missing: '@required'. See `drush help @command` for information on usage.", array('@missing' => $missing, '@required' => $required, '@command' => $command['command']))); } } return TRUE; } /** * Return the list of all of the options for the given * command record by merging the 'options' and 'sub-options' * records. */ function _drush_get_command_options($command) { drush_command_invoke_all_ref('drush_help_alter', $command); $options = $command['options']; foreach ($command['sub-options'] as $group => $suboptions) { $options = array_merge($options, $suboptions); } return $options; } /** * Return the array keys of $options, plus any 'short-form' * representations that may appear in the option's value. */ function _drush_flatten_options($options) { $flattened_options = array(); foreach($options as $key => $value) { // engine sections start with 'package-handler=git_drupalorg', // or something similar. Get rid of everything from the = onward. if (($eq_pos = strpos($key, '=')) !== FALSE) { $key = substr($key, 0, $eq_pos); } $flattened_options[] = $key; if (is_array($value)) { if (array_key_exists('short-form', $value)) { $flattened_options[] = $value['short-form']; } } } return $flattened_options; } /** * Get the options that were passed to the current command. * * This function returns an array that contains all of the options * that are appropriate for forwarding along to drush_invoke_process. * * @return * An associative array of option key => value pairs. */ function drush_redispatch_get_options() { // Start off by taking everything from the site alias and command line // ('cli' context) $cli_context = drush_get_context('cli'); // local php settings should not override sitealias settings unset($cli_context['php']); unset($cli_context['php-options']); // cli overrides sitealias and command specific $options = $cli_context + drush_get_context('alias') + drush_get_context('specific'); $options = array_diff_key($options, array_flip(drush_sitealias_site_selection_keys())); unset($options['command-specific']); unset($options['path-aliases']); // If we can parse the current command, then examine all contexts // in order for any option that is directly related to the current command $command = drush_parse_command(); if (is_array($command)) { foreach ($command['options'] as $key => $value) { // Strip leading -- $key = ltrim($key, '-'); $value = drush_get_option($key); if (isset($value)) { $options[$key] = $value; } } } // If --bootstrap-to-first-arg is specified, do not // pass it along to remote commands. unset($options['bootstrap-to-first-arg']); return $options; } /** * @} End of "defgroup dispatching". */ /** * @file * The drush command engine. * * Since drush can be invoked independently of a proper Drupal * installation and commands may operate across sites, a distinct * command engine is needed. * * It mimics the Drupal module engine in order to economize on * concepts and to make developing commands as familiar as possible * to traditional Drupal module developers. */ /** * Parse console arguments. */ function drush_parse_args() { $args = drush_get_context('argv'); $command_args = NULL; $global_options = array(); $target_alias_name = NULL; // It would be nice if commandfiles could somehow extend this list, // but it is not possible. We need to parse args before we find commandfiles, // because the specified options may affect how commandfiles are located. // Therefore, commandfiles are loaded too late to affect arg parsing. // There are only a limited number of short options anyway; drush reserves // all for use by drush core. static $arg_opts = array('c', 'u', 'r', 'l', 'i'); // Check to see if we were executed via a "#!/usr/bin/env drush" script drush_adjust_args_if_shebang_script($args); // Now process the command line arguments. We will divide them // into options (starting with a '-') and arguments. $arguments = $options = array(); for ($i = 1; $i < count($args); $i++) { $opt = $args[$i]; // We set $command_args to NULL until the first argument that is not // an alias is found (the command); we put everything that follows // into $command_args. if (is_array($command_args)) { $command_args[] = $opt; } // Is the arg an option (starting with '-')? if (!empty($opt) && $opt{0} == "-" && strlen($opt) != 1) { // Do we have multiple options behind one '-'? if (strlen($opt) > 2 && $opt{1} != "-") { // Each char becomes a key of its own. for ($j = 1; $j < strlen($opt); $j++) { $options[substr($opt, $j, 1)] = true; } } // Do we have a longopt (starting with '--')? elseif ($opt{1} == "-") { if ($pos = strpos($opt, '=')) { $options[substr($opt, 2, $pos - 2)] = substr($opt, $pos + 1); } else { $options[substr($opt, 2)] = true; } } else { $opt = substr($opt, 1); // Check if the current opt is in $arg_opts (= has to be followed by an argument). if ((in_array($opt, $arg_opts))) { // Raising errors for missing option values should be handled by the // bootstrap or specific command, so we no longer do this here. $options[$opt] = $args[$i + 1]; $i++; } else { $options[$opt] = true; } } } // If it's not an option, it's a command. else { $arguments[] = $opt; // Once we find the first argument, record the command args and global options if (!is_array($command_args)) { // Remember whether we set $target_alias_name on a previous iteration, // then record the $target_alias_name iff this arguement references a valid site alias. $already_set_target = is_string($target_alias_name); if (!$already_set_target && drush_sitealias_valid_alias_format($opt)) { $target_alias_name = $opt; } // If an alias record was set on a previous iteration, then this // argument must be the command name. If we set the target alias // record on this iteration, then this is not the command name. // If we've found the command name, then save $options in $global_options // (all options that came before the command name), and initialize // $command_args to an array so that we will begin storing all args // and options that follow the command name in $command_args. if ($already_set_target || (!is_string($target_alias_name))) { $command_args = array(); $global_options = $options; } } } } // If no arguments are specified, then the command will // be either 'help' or 'version' (the later if --version is specified) if (!sizeof($arguments)) { if (array_key_exists('version', $options)) { $arguments = array('version'); } else { $arguments = array('help'); } } if (is_array($command_args)) { drush_set_context('DRUSH_COMMAND_ARGS', $command_args); } drush_set_context('DRUSH_GLOBAL_CLI_OPTIONS', $global_options); // Handle the "@shift" alias, if present drush_process_bootstrap_to_first_arg($arguments); drush_set_arguments($arguments); drush_set_config_special_contexts($options); drush_set_context('cli', $options); return $arguments; } /** * Pop an argument off of drush's argument list */ function drush_shift() { $arguments = drush_get_arguments(); $result = NULL; if (!empty($arguments)) { // The php-script command uses the DRUSH_SHIFT_SKIP // context to cause drush_shift to skip the 'php-script' // command and the script path argument when it is // called from the user script. $skip_count = drush_get_context('DRUSH_SHIFT_SKIP'); if (is_numeric($skip_count)) { for ($i = 0; $i < $skip_count; $i++) { array_shift($arguments); } $skip_count = drush_set_context('DRUSH_SHIFT_SKIP', 0); } $result = array_shift($arguments); drush_set_arguments($arguments); } return $result; } /** * Special checking for "shebang" script handling. * * If there is a file 'script.php' that begins like so: * #!/path/to/drush * Then $args will be: * /path/to/drush /path/to/script userArg1 userArg2 ... * If it instead starts like this: * #!/path/to/drush --flag php-script * Then $args will be: * /path/to/drush "--flag php-script" /path/to/script userArg1 userArg2 ... * (Note that execve does not split the parameters from * the shebang line on whitespace; see http://en.wikipedia.org/wiki/Shebang_%28Unix%29) * When drush is called via one of the "shebang" lines above, * the first or second parameter will be the full path * to the "shebang" script file -- and if the path to the * script is in the second position, then we will expect that * the argument in the first position must begin with a * '@' (alias) or '-' (flag). Under ordinary circumstances, * we do not expect that the drush command must come before * any argument that is the full path to a file. We use * this assumption to detect "shebang" script execution. */ function drush_adjust_args_if_shebang_script(&$args) { if (drush_has_bash()) { // The drush script may add --php or --php-options at the // head of the argument list; skip past those. $base_arg_number = 1; while (substr($args[$base_arg_number], 0, 5) == '--php') { ++$base_arg_number; } if (_drush_is_drush_shebang_script($args[$base_arg_number])) { // If $args[1] is a drush "shebang" script, we will insert // the option "--bootstrap-to-first-arg" and the command // "php-script" at the beginning of @args, so the command // line args become: // /path/to/drush --bootstrap-to-first-arg php-script /path/to/script userArg1 userArg2 ... drush_set_option('bootstrap-to-first-arg', TRUE); array_splice($args, $base_arg_number, 0, array('php-script')); drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE); } elseif (((strpos($args[$base_arg_number], ' ') !== FALSE) || (!ctype_alnum($args[$base_arg_number][0]))) && (_drush_is_drush_shebang_script($args[$base_arg_number + 1]))) { // If $args[2] is a drush "shebang" script, we will insert // the space-exploded $arg[1] in place of $arg[1], so the // command line args become: // /path/to/drush scriptArg1 scriptArg2 ... /path/to/script userArg1 userArg2 ... // If none of the script arguments look like a drush command, // then we will insert "php-script" as the default command to // execute. $script_args = explode(' ', $args[$base_arg_number]); $has_command = FALSE; foreach ($script_args as $script_arg) { if (preg_match("/^[a-z][a-z0-9-]*$/",$script_arg)) { $has_command = TRUE; } } if (!$has_command) { $script_args[] = 'php-script'; } array_splice($args, 1, $base_arg_number, $script_args); drush_set_context('DRUSH_SHEBANG_SCRIPT', TRUE); } } } /** * Process the --bootstrap-to-first-arg option, if it is present. * * This option checks to see if the first user-provided argument is an alias * or site specification; if it is, it will be shifted into the first argument * position, where it will specify the site to bootstrap. The result of this * is that if your shebang line looks like this: * * #!/path/to/drush --bootstrap-to-first-arg php-script * * Then when you run that script, you can optionally provide an alias such * as @dev as the first argument (e.g. $ ./mydrushscript.php @dev scriptarg1 * scriptarg2). Since this is the behavior that one would usually want, * it is default behavior for a canonical script. That is, a script * with a simple shebang line, like so: * * #!/path/to/drush * * will implicitly have "--bootstrap-to-first-arg" and "php-script" prepended, and will therefore * behave exactly like the first example. To write a script that does not * use --bootstrap-to-first-arg, then the drush command or at least one flag must be explicitly * included, like so: * * #!/path/to/drush php-script */ function drush_process_bootstrap_to_first_arg(&$arguments) { if (drush_get_option('bootstrap-to-first-arg', FALSE)) { $shift_alias_pos = 1 + (drush_get_context('DRUSH_SHEBANG_SCRIPT') === TRUE); if (sizeof($arguments) >= $shift_alias_pos) { $shifted_alias = $arguments[$shift_alias_pos]; $alias_record = drush_sitealias_get_record($shifted_alias); if (!empty($alias_record)) { // Move the alias we shifted from its current position // in the argument list to the front of the list array_splice($arguments, $shift_alias_pos, 1); array_unshift($arguments, $shifted_alias); } } } } /** * Get the short commandfile name that matches the * command. * * @param $command * The name of the command (e.g. search-index) * @return String * The short commandfile name where that command was * defined (e.g. search, not search.drush.inc) */ function drush_get_commandfile_for_command($command) { $commandfile = FALSE; $commands = drush_get_commands(); if (array_key_exists($command, $commands)) { $commandfile = $commands[$command]['commandfile']; } return $commandfile; } /** * Get a list of all implemented commands. * This invokes hook_drush_command(). * * @return * Associative array of currently active command descriptors. * */ function drush_get_commands() { $commands = $available_commands = array(); $list = drush_commandfile_list(); foreach ($list as $commandfile => $path) { if (drush_command_hook($commandfile, 'drush_command')) { $function = $commandfile . '_drush_command'; $result = $function(); foreach ((array)$result as $key => $command) { // Add some defaults and normalize the command descriptor. $command += drush_command_defaults($key, $commandfile, $path); // Add engine data. _drush_merge_engine_data($command); // Translate command. drush_command_translate($command); // If command callback function name begins with "drush_$commandfile_", // then fix up the command entry so that drush_invoke will be // called by way of drush_command. This will cause all // of the applicable hook functions to be called for the // command when it is invoked. If the callback function does // -not- begin with its commandfile name, then it will be // called directly by drush_dispatch, and no hook functions // will be called (e.g. you cannot hook drush_print_file). if ($command['callback'] != 'drush_command') { $required_command_prefix = 'drush_' . $commandfile . '_'; if ((substr($command['callback'], 0, strlen($required_command_prefix)) == $required_command_prefix)) { $command['command-hook'] = substr($command['callback'], strlen('drush_')); $command['callback'] = 'drush_command'; } } $commands[$key] = $command; // For every alias, make a copy of the command and store it in the command list // using the alias as a key if (isset($command['aliases']) && count($command['aliases'])) { foreach ($command['aliases'] as $alias) { $commands[$alias] = $command; $commands[$alias]['is_alias'] = TRUE; } } } } } return drush_set_context('DRUSH_COMMANDS', $commands); } function drush_command_defaults($key, $commandfile, $path) { return array( 'command' => $key, 'command-hook' => $key, 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, 'callback arguments' => array(), 'commandfile' => $commandfile, 'path' => dirname($path), 'engines' => array(), // Helpful for drush_show_help(). 'callback' => 'drush_command', 'description' => NULL, 'sections' => array( 'examples' => 'Examples', 'arguments' => 'Arguments', 'options' => 'Options', ), 'arguments' => array(), 'required-arguments' => FALSE, 'options' => array(), 'sub-options' => array(), 'allow-additional-options' => FALSE, 'examples' => array(), 'aliases' => array(), 'core' => array(), 'scope' => 'site', 'drupal dependencies' => array(), 'drush dependencies' => array(), 'handle-remote-commands' => FALSE, 'strict-option-handling' => FALSE, 'bootstrap_errors' => array(), 'topics' => array(), 'hidden' => FALSE, ); } /** * Translates description and other keys of a command definition. * * @param $command * A command definition. */ function drush_command_translate(&$command) { $command['description'] = _drush_command_translate($command['description']); $keys = array('arguments', 'options', 'examples', 'sections'); foreach ($keys as $key) { foreach ($command[$key] as $k => $v) { if (is_array($v)) { $v['description'] = _drush_command_translate($v['description']); } else { $v = _drush_command_translate($v); } $command[$key][$k] = $v; } } } /** * Helper function for drush_command_translate(). * * @param $source * String or array. */ function _drush_command_translate($source) { return is_array($source) ? call_user_func_array('dt', $source) : dt($source); } /** * Matches a commands array, as returned by drush_get_arguments, with the * current command table. * * Note that not all commands may be discoverable at the point-of-call, * since Drupal modules can ship commands as well, and they are * not available until after bootstrapping. * * drush_parse_command returns a normalized command descriptor, which * is an associative array. Some of its entries are: * - callback arguments: an array of arguments to pass to the calback. * - description: description of the command. * - arguments: an array of arguments that are understood by the command. for help texts. * - required-arguments: The minimum number of arguments that are required, or TRUE if all are required. * - options: an array of options that are understood by the command. for help texts. * - examples: an array of examples that are understood by the command. for help texts. * - scope: one of 'system', 'project', 'site'. * - bootstrap: drupal bootstrap level (depends on Drupal major version). -1=no_bootstrap. * - core: Drupal major version required. * - drupal dependencies: drupal modules required for this command. * - drush dependencies: other drush command files required for this command. * - handle-remote-commands: set to TRUE if `drush @remote mycommand` should be executed * locally rather than remotely dispatched. When this mode is set, the target site * can be obtained via: * drush_get_context('DRUSH_TARGET_SITE_ALIAS') * - strict-option-handling: set to TRUE if drush should strictly separate local command * cli options from the global options. Usually, drush allows global cli options and * command cli options to be interspersed freely on the commandline. For commands where * this flag is set, options are separated, with global options comming before the * command names, and command options coming after, like so: * drush --global-options command --command-options * In this mode, the command options are no longer available via drush_get_option(); * instead, they can be retrieved via: * $args = drush_get_original_cli_args_and_options(); * $args = drush_get_context('DRUSH_COMMAND_ARGS', array()); * In this case, $args will contain the command args and options literally, exactly as they * were entered on the command line, and in the same order as they appeared. */ function drush_parse_command() { $args = drush_get_arguments(); $command = FALSE; // Get a list of all implemented commands. $implemented = drush_get_commands(); if (isset($implemented[$args[0]])) { $command = $implemented[$args[0]]; $arguments = array_slice($args, 1); } // We have found a command that matches. Set the appropriate values. if ($command) { // Special case. Force help command if --help option was specified. if (drush_get_option('help')) { $arguments = array($command['command']); $command = $implemented['help']; $command['arguments'] = $arguments; } else { _drush_prepare_command($command, $arguments); } drush_set_command($command); } return $command; } /* * Called by drush_parse_command. If a command is dispatched * directly by drush_dispatch, then drush_dispatch will call * this function. */ function _drush_prepare_command(&$command, $arguments = array()) { // Drush overloads $command['arguments']; save the argument description if (!isset($command['argument-description'])) { $command['argument-description'] = $command['arguments']; } // Merge specified callback arguments, which precede the arguments passed on the command line. if (isset($command['callback arguments']) && is_array($command['callback arguments'])) { $arguments = array_merge($command['callback arguments'], $arguments); } $command['arguments'] = $arguments; } /** * Invoke a hook in all available command files that implement it. * * @see drush_command_invoke_all_ref() * * @param $hook * The name of the hook to invoke. * @param ... * Arguments to pass to the hook. * @return * An array of return values of the hook implementations. If commands return * arrays from their implementations, those are merged into one array. */ function drush_command_invoke_all() { $args = func_get_args(); if (count($args) == 1) { $args[] = NULL; } $reference_value = $args[1]; $args[1] = &$reference_value; return call_user_func_array('drush_command_invoke_all_ref', $args); } /** * A drush_command_invoke_all() that wants the first parameter to be passed by reference. * * @see drush_command_invoke_all() */ function drush_command_invoke_all_ref($hook, &$reference_parameter) { $args = func_get_args(); array_shift($args); // Insure that call_user_func_array can alter first parameter $args[0] = &$reference_parameter; $return = array(); foreach (drush_command_implements($hook) as $module) { $function = $module .'_'. $hook; $result = call_user_func_array($function, $args); if (isset($result) && is_array($result)) { $return = array_merge_recursive($return, $result); } else if (isset($result)) { $return[] = $result; } } return $return; } /** * Determine which command files are implementing a hook. * * @param $hook * The name of the hook (e.g. "drush_help" or "drush_command"). * * @return * An array with the names of the command files which are implementing this hook. */ function drush_command_implements($hook) { $implementations[$hook] = array(); $list = drush_commandfile_list(); foreach ($list as $commandfile => $file) { if (drush_command_hook($commandfile, $hook)) { $implementations[$hook][] = $commandfile; } } return (array)$implementations[$hook]; } /** * @param string * name of command to check. * * @return boolean * TRUE if the given command has an implementation. */ function drush_is_command($command) { $commands = drush_get_commands(); return isset($commands[$command]); } /** * Collect a list of all available drush command files. * * Scans the following paths for drush command files: * * - The "/path/to/drush/commands" folder. * - Folders listed in the 'include' option (see example.drushrc.php). * - The system-wide drush commands folder, e.g. /usr/share/drush/commands * - The ".drush" folder in the user's HOME folder. * - sites/all/drush in current Drupal site. * - Folders belonging to enabled modules in the current Drupal site. * * Commands implementing hook_drush_load() in MODULE.drush.load.inc with * a return value FALSE will not be loaded. * * A Drush command file is a file that matches "*.drush.inc". * * @see drush_scan_directory() * * @return * An associative array whose keys and values are the names of all available * command files. */ function drush_commandfile_list() { return drush_get_context('DRUSH_COMMAND_FILES', array()); } function _drush_find_commandfiles($phase, $phase_max = FALSE) { if (!$phase_max) { $phase_max = $phase; } $searchpath = array(); switch ($phase) { case DRUSH_BOOTSTRAP_DRUSH: // Core commands shipping with drush $searchpath[] = realpath(dirname(__FILE__) . '/../commands/'); // User commands, specified by 'include' option if ($include = drush_get_context('DRUSH_INCLUDE', FALSE)) { foreach ($include as $path) { if (is_dir($path)) { drush_log('Include ' . $path, 'notice'); $searchpath[] = $path; } } } // System commands, residing in $SHARE_PREFIX/share/drush/commands $share_path = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES'); if (is_dir($share_path)) { $searchpath[] = $share_path; } // User commands, residing in ~/.drush $per_user_config_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION'); if (!empty($per_user_config_dir)) { $searchpath[] = $per_user_config_dir; } // Include commandfiles located in Drupal's sites/all/drush even before root is bootstrapped. if ($drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT')) { $searchpath[] = $drupal_root . '/sites/all/drush'; } break; case DRUSH_BOOTSTRAP_DRUPAL_SITE: // If we are going to stop bootstrapping at the site, then // we will quickly add all commandfiles that we can find for // any module associated with the site, whether it is enabled // or not. If we are, however, going to continue on to bootstrap // all the way to DRUSH_BOOTSTRAP_DRUPAL_FULL, then we will // instead wait for that phase, which will more carefully add // only those Drush command files that are associated with // enabled modules. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) { $searchpath[] = conf_path() . '/modules'; // Add all module paths, even disabled modules. Prefer speed over accuracy. $searchpath[] = 'sites/all/modules'; } if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION) { // Too early for variable_get('install_profile', 'default'); look in all profiles. // If we're definitely going to get to DRUPAL_CONFIGURATION, then wait and // see if we have 'install_profile' available there. $searchpath[] = "profiles"; } // Planning ahead for Drush scripts in core Drupal. if (drush_drupal_major_version() >=8) { $searchpath[] = 'modules'; } $searchpath[] = 'sites/all/themes'; $searchpath[] = conf_path() . '/themes'; break; case DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION: // See comment above regarding this if() condition. if ($phase_max < DRUSH_BOOTSTRAP_DRUPAL_FULL) { // You must define your install_profile in settings.php. The DB is not sufficient. // Drupal core does not yet do that. See http://drupal.org/node/545452. if ($profile = variable_get('install_profile', NULL)) { $searchpath[] = "profiles/$profile/modules"; } else { // If 'install_profile' is not available, check all profiles. $searchpath[] = "profiles"; } } break; case DRUSH_BOOTSTRAP_DRUPAL_FULL: // Add enabled module paths. Since we are bootstrapped, // we can use the Drupal API. $ignored_modules = drush_get_option_list('ignored-modules', array()); foreach (array_diff(module_list(), $ignored_modules) as $module) { $filename = drupal_get_filename('module', $module); $searchpath[] = dirname($filename); } break; } _drush_add_commandfiles($searchpath, $phase); } function _drush_add_commandfiles($searchpath, $phase = NULL, $reset = FALSE) { static $evaluated = array(); static $deferred = array(); $cache =& drush_get_context('DRUSH_COMMAND_FILES', array()); if (sizeof($searchpath)) { if (!$reset) { // Assemble a cid specific to the bootstrap phase and searchpaths. $cid = drush_get_cid('commandfiles-' . $phase, array(), $searchpath); $command_cache = drush_cache_get($cid); if (isset($command_cache->data)) { $cached_list = $command_cache->data; // Take drush_make out of the cache to help people transitioning // from using drush make as a contrib module to drush make in core. // See: http://drupal.org/node/1366746 unset($cached_list['drush_make']); // If we want to temporarily ignore modules via 'ignored-modules', // then we need to take these out of the cache as well. foreach (drush_get_option_list('ignored-modules') as $ignored) { unset($cached_list[$ignored]); } } } // Build a list of all of the modules to attempt to load. // Start with any modules deferred from a previous phase. $list = $deferred; if (isset($cached_list)) { $list = array_merge($list, $cached_list); } else { // Scan for drush command files; add to list for consideration if found. foreach (array_unique($searchpath) as $path) { if (is_dir($path)) { $nomask = array_merge(drush_filename_blacklist(), drush_get_option_list('ignored-modules')); $dmv = DRUSH_MAJOR_VERSION; $files = drush_scan_directory($path, "/\.drush($dmv|)\.inc$/", $nomask); foreach ($files as $filename => $info) { $module = basename($filename); $module = str_replace(array('.drush.inc', ".drush$dmv.inc"), '', $module); // Only try to bootstrap modules that we have never seen before, or that we // have tried to load but did not due to an unmet _drush_load() requirement. if (!array_key_exists($module, $evaluated) && file_exists($filename)) { $evaluated[$module] = TRUE; $list[$module] = $filename; } } } } if (isset($cid)) { drush_cache_set($cid, $list); } } // Check each file in the consideration list; if there is // a modulename_drush_load() function in modulename.drush.load.inc, // then call it to determine if this file should be loaded. foreach ($list as $module => $filename) { $load_command = TRUE; $load_test_inc = dirname($filename) . "/" . $module . ".drush.load.inc"; if (file_exists($load_test_inc)) { include_once($load_test_inc); $load_test_func = $module . "_drush_load"; if (function_exists($load_test_func)) { $load_command = $load_test_func($phase); } } if ($load_command) { // Only try to require if the file exists. If not, a file from the // command file cache may not be available anymore, in which case // we rebuild the cache for this phase. if ($filepath = realpath($filename)) { require_once $filepath; unset($deferred[$module]); } elseif (!$reset) { _drush_add_commandfiles($searchpath, $phase, TRUE); } } else { unset($list[$module]); // Signal that we should try again on // the next bootstrap phase. We set // the flag to the filename of the first // module we find so that only that one // will be retried. $deferred[$module] = $filename; } } if (sizeof($list)) { $cache = array_merge($cache, $list); ksort($cache); } } } /** * Substrings to ignore during commandfile searching. */ function drush_filename_blacklist() { return array_diff(array('.', '..', 'drush_make', 'examples', 'tests', 'disabled', 'gitcache', 'cache', 'drush4', 'drush5', 'drush6', 'drush7'), array('drush' . DRUSH_MAJOR_VERSION)); } /** * Conditionally include files based on the command used. * * Steps through each of the currently loaded commandfiles and * loads an optional commandfile based on the key. * * When a command such as 'pm-enable' is called, this * function will find all 'enable.pm.inc' files that * are present in each of the commandfile directories. */ function drush_command_include($command) { $include_files = drush_command_get_includes($command); foreach($include_files as $filename => $commandfile) { drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap'); include_once($filename); } } function drush_command_get_includes($command) { $include_files = array(); $parts = explode('-', $command); $command = implode(".", array_reverse($parts)); $commandfiles = drush_commandfile_list(); $options = array(); foreach ($commandfiles as $commandfile => $file) { $filename = sprintf("%s/%s.inc", dirname($file), $command); if (file_exists($filename)) { $include_files[$filename] = $commandfile; } } return $include_files; } /** * Conditionally include default options based on the command used. */ function drush_command_default_options($command = NULL) { $command_default_options = drush_get_context('command-specific'); return drush_command_set_command_specific($command_default_options, $command); } function drush_sitealias_command_default_options($site_record, $prefix, $command = NULL) { if (isset($site_record) && array_key_exists($prefix . 'command-specific', $site_record)) { return drush_command_set_command_specific($site_record[$prefix . 'command-specific'], $command); } return FALSE; } function drush_command_set_command_specific($command_default_options, $command = NULL) { if (!$command) { $command = drush_get_command(); } if ($command) { // Look for command-specific options for this command // keyed both on the command's primary name, and on each // of its aliases. $options_were_set = _drush_command_set_default_options($command_default_options, $command['command']); if (isset($command['aliases']) && count($command['aliases'])) { foreach ($command['aliases'] as $alias) { $options_were_set += _drush_command_set_default_options($command_default_options, $alias); } } // If we set or cleared any options, go back and re-bootstrap any global // options such as -y and -v. if (!empty($options_were_set)) { _drush_bootstrap_global_options(); } // If the command uses strict option handling, back out any global // options that were set. if ($command['strict-option-handling']) { $global_options = drush_get_global_options(); foreach ($options_were_set as $key) { if (array_key_exists($key, $global_options)) { if (!array_key_exists('context', $global_options[$key])) { $strict_options_warning =& drush_get_context('DRUSH_STRICT_OPTIONS_WARNING', array()); if (!array_key_exists($key, $strict_options_warning)) { drush_log(dt("Global option --!option not supported in command-specific options for command !command due to a limitation in strict option handling.", array('!option' => $key, '!command' => $command['command'])), 'warning'); $strict_options_warning[$key] = TRUE; } } drush_unset_option($key, 'specific'); } } } } } function _drush_command_set_default_options($command_default_options, $command) { $options_were_set = array(); if (array_key_exists($command, $command_default_options)) { foreach ($command_default_options[$command] as $key => $value) { // We set command-specific options in their own context // that is higher precedence than the various config file // context, but lower than command-line options. if (!drush_get_option('no-' . $key, FALSE)) { drush_set_option($key, $value, 'specific'); $options_were_set[] = $key; } } } return $options_were_set; } /** * Return the original cli args and options, exactly as they * appeared on the command line, and in the same order. * Any command-specific options that were set will also * appear in this list, appended at the very end. * * The args and options returned are raw, and must be * escaped as necessary before use. */ function drush_get_original_cli_args_and_options($command = NULL) { $args = drush_get_context('DRUSH_COMMAND_ARGS', array()); $command_specific_options = drush_get_context('specific'); if ($command == NULL) { $command = drush_get_command(); } $command_options = ($command == NULL) ? array() : _drush_get_command_options($command); foreach ($command_specific_options as $key => $value) { if (!array_key_exists($key, $command_options)) { if (($value === TRUE) || (!isset($value))) { $args[] = "--$key"; } else { $args[] = "--$key=$value"; } } } return $args; } /** * Determine whether a command file implements a hook. * * @param $module * The name of the module (without the .module extension). * @param $hook * The name of the hook (e.g. "help" or "menu"). * @return * TRUE if the the hook is implemented. */ function drush_command_hook($commandfile, $hook) { return function_exists($commandfile . '_' . $hook); } /** * Check that a command is valid for the current bootstrap phase. * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * * @return * TRUE if command is valid. */ function drush_enforce_requirement_bootstrap_phase(&$command) { $valid = array(); $current_phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); if ($command['bootstrap'] <= $current_phase) { return TRUE; } // TODO: provide description text for each bootstrap level so we can give // the user something more helpful and specific here. $command['bootstrap_errors']['DRUSH_COMMAND_INSUFFICIENT_BOOTSTRAP'] = dt('Command !command needs a higher bootstrap level to run - you will need to invoke drush from a more functional Drupal environment to run this command.', array('!command' => $command['command'])); } /** * Check that a command has its declared dependencies available or have no * dependencies. * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * * @return * TRUE if command is valid. */ function drush_enforce_requirement_drupal_dependencies(&$command) { // If the command bootstrap is DRUSH_BOOTSTRAP_MAX, then we will // allow the requirements to pass if we have not successfully // bootstrapped Drupal. The combination of DRUSH_BOOTSTRAP_MAX // and 'drupal dependencies' indicates that the drush command // will use the dependent modules only if they are available. if ($command['bootstrap'] == DRUSH_BOOTSTRAP_MAX) { // If we have not bootstrapped, then let the dependencies pass; // if we have bootstrapped, then enforce them. if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') < DRUSH_BOOTSTRAP_DRUPAL_FULL) { return TRUE; } } // If there are no drupal dependencies, then do nothing if (!empty($command['drupal dependencies'])) { foreach ($command['drupal dependencies'] as $dependency) { if(!function_exists('module_exists') || !module_exists($dependency)) { $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']))); return FALSE; } } } return TRUE; } /** * Check that a command has its declared drush dependencies available or have no * dependencies. Drush dependencies are helpful when a command is invoking * another command, or implementing its API. * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * @return * TRUE if dependencies are met. */ function drush_enforce_requirement_drush_dependencies(&$command) { // If there are no drush dependencies, then do nothing. if (!empty($command['drush dependencies'])) { $commandfiles = drush_commandfile_list(); foreach ($command['drush dependencies'] as $dependency) { if (!isset($commandfiles[$dependency])) { $dt_args = array( '!command' => $command['command'], '!dependency' => "$dependency.drush.inc", ); $command['bootstrap_errors']['DRUSH_COMMANDFILE_DEPENDENCY_ERROR'] = dt('Command !command needs the following drush command file to run: !dependency.', $dt_args); return FALSE; } } } return TRUE; } /** * Check that a command is valid for the current major version of core. Handles * explicit version numbers and 'plus' numbers like 7+ (compatible with 7,8 ...). * * @param $command * Command to check. Any errors will be added to the 'bootstrap_errors' element. * * @return * TRUE if command is valid. */ function drush_enforce_requirement_core(&$command) { $major = drush_drupal_major_version(); if (!$core = $command['core']) { return TRUE; } foreach ($core as $compat) { if ($compat == $major) { return TRUE; } elseif (substr($compat, -1) == '+' && $major >= substr($compat, 0, strlen($compat)-1)) { return TRUE; } } $versions = array_pop($core); if (!empty($core)) { $versions = implode(', ', $core) . dt(' or ') . $versions; } $command['bootstrap_errors']['DRUSH_COMMAND_CORE_VERSION_ERROR'] = dt('Command !command requires Drupal core version !versions to run.', array('!command' => $command['command'], '!versions' => $versions)); } /* * Check if a shell alias exists for current request. If so, re-route to * core-execute like the and pass alias value along with rest of CLI arguments. */ function drush_shell_alias_replace() { $args = drush_get_arguments(); $argv = drush_get_context('argv'); $first = current($args); // @todo drush_get_option is awkward here. $shell_aliases = drush_get_context('shell-aliases', array()); if (isset($shell_aliases[$first])) { // Shell alias found for first argument in the request. $alias_value = $shell_aliases[$first]; if (!is_array($alias_value)) { // Shell aliases can have embedded variables such as {{@target}} and {{%root}} // that are replaced with the name of the target site alias, or the value of a // path alias defined in the target site alias record. We only support replacements // when the alias value is a string; if it is already broken out into an array, // then the values therein are used literally. $alias_variables = array( '{{@target}}' => '@none' ); $target_site_alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS', FALSE); if ($target_site_alias) { $alias_variables = array( '{{@target}}' => $target_site_alias ); $target = drush_sitealias_get_record($target_site_alias); foreach ($target as $key => $value) { if (!is_array($value)) { $alias_variables['{{' . $key . '}}'] = $value; } } if (array_key_exists('path-aliases', $target)) { foreach ($target['path-aliases'] as $key => $value) { // n.b. $key will contain something like "%root" or "%files". $alias_variables['{{' . $key . '}}'] = $value; } } } $alias_value = str_replace(array_keys($alias_variables), array_values($alias_variables), $alias_value); // Check for unmatched replacements $matches = array(); $match_result = preg_match('/{{[%@#]*[a-z0-9.]*}}/', $alias_value, $matches); if ($match_result) { $unmatched_replacements = implode(', ', $matches); $unmatched_replacements = preg_replace('/[{}]/', '', $unmatched_replacements); return drush_set_error('DRUSH_SHELL_ALIAS_UNMATCHED_REPLACEMENTS', dt('The shell alias @alias-name uses replacements "@unmatched". You must use this command with a site alias (e.g. `drush @myalias @alias-name ...`) that defines all of these variables.', array('@alias-name' => $first, '@unmatched' => $unmatched_replacements))); } if (function_exists('str_getcsv')) { $alias_value = str_getcsv($alias_value, ' '); } else { $alias_value = explode(' ', $alias_value); } } drush_log(dt('Shell alias found: !key => !value', array('!key' => $first, '!value' => implode(' ', $alias_value))), 'debug'); $replacement = $alias_value; if (substr($alias_value[0], 0, 1) == '!') { $replacement[0] = ltrim($replacement[0], '!'); array_unshift($replacement, 'core-execute'); } $pos = array_search($first, $argv); array_splice($argv, $pos, 1, $replacement); drush_set_context('argv', $argv); drush_parse_args(); _drush_bootstrap_global_options(); } } drush-5.10.0/includes/complete.inc000066400000000000000000000520701222105546100170350ustar00rootroot00000000000000. The shell completion scripts should call * "drush complete ", where is the full command line, which we take * as input and use to produce a list of possible completions for the * current/next word, separated by newlines. Typically, when multiple * completions are returned the shell will display them to the user in a concise * format - but when a single completion is returned it will autocomplete. * * We provide completion for site aliases, commands, shell aliases, options, * engines and arguments. Displaying all of these when the last word has no * characters yet is not useful, as there are too many items. Instead we filter * the possible completions based on position, in a similar way to git. * For example: * - We only display site aliases and commands if one is not already present. * - We only display options if the user has already entered a hyphen. * - We only display global options before a command is entered, and we only * display command specific options after the command (Drush itself does not * care about option placement, but this approach keeps things more concise). * * Below is typical output of complete in different situations. Tokens in square * brackets are optional, and [word] will filter available options that start * with the same characters, or display all listed options if empty. * drush --[word] : Output global options * drush [word] : Output site aliases, sites, commands and shell aliases * drush [@alias] [word] : Output commands * drush [@alias] command [word] : Output command specific arguments * drush [@alias] command --[word] : Output command specific options * * Because the purpose of autocompletion is to make the command line more * efficient for users we need to respond quickly with the list of completions. * To do this, we call drush_complete() early in the Drush bootstrap, and * implement a simple caching system. * * To generate the list of completions, we set up the Drush environment as if * the command was called on it's own, parse the command using the standard * Drush functions, bootstrap the site (if any) and collect available * completions from various sources. Because this can be somewhat slow, we cache * the results. The cache strategy aims to balance accuracy and responsiveness: * - We cache per site, if a site is available. * - We generate (and cache) everything except arguments at the same time, so * subsequent completions on the site don't need any bootstrap. * - We generate and cache arguments on-demand, since these can often be * expensive to generate. Arguments are also cached per-site. * * For argument completions, commandfiles can implement * COMMANDFILE_COMMAND_complete() returning an array containing a key 'values' * containing an array of all possible argument completions for that command. * For example, return array('values' => array('aardvark', 'aardwolf')) offers * the words 'aardvark' and 'aardwolf', or will complete to 'aardwolf' if the * letters 'aardw' are already present. Since command arguments are cached, * commandfiles can bootstrap a site or perform other somewhat time consuming * activities to retrieve the list of possible arguments. Commands can also * clear the cache (or just the "arguments" cache for their command) when the * completion results have likely changed - see drush_complete_cache_clear(). * * Commandfiles can also return a special optional element in their array with * the key 'files' that contains an array of patterns/flags for the glob() * function. These are used to produce file and directory completions (the * results of these are not cached, since this is a fast operation). * See http://php.net/glob for details of valid patterns and flags. * For example the following will complete the command arguments on all * directories, as well as files ending in tar.gz: * return array( * 'files' => array( * 'directories' => array( * 'pattern' => '*', * 'flags' => GLOB_ONLYDIR, * ), * 'tar' => array( * 'pattern' => '*.tar.gz', * ), * ), * ); * * To check completion results without needing to actually trigger shell * completion, you can call this manually using a command like: * * drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... * * If you want to simulate the results of pressing tab after a space (i.e. * and empty last word, include '' on the end of your command: * * drush --early=includes/complete.inc [--complete-debug] drush '' */ /** * Produce autocomplete output. * * Determine position (is there a site-alias or command set, and are we trying * to complete an option). Then produce a list of completions for the last word * and output them separated by newlines. */ function drush_early_complete() { // We use a distinct --complete-debug option to avoid unwanted debug messages // being printed when users use this option for other purposes in the command // they are trying to complete. drush_set_option('debug', FALSE); if (drush_get_option('complete-debug', FALSE)) { drush_set_context('DRUSH_DEBUG', TRUE); } // Set up as if we were running the command, and attempt to parse. $argv = drush_complete_process_argv(); if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) { $set_sitealias_name = $alias; $set_sitealias = drush_sitealias_get_record($alias); } // Arguments have now had site-aliases and options removed, so we take the // first item as our command. We need to know if the command is valid, so that // we know if we are supposed to complete an in-progress command name, or // arguments for a command. We do this by checking against our per-site cache // of command names (which will only bootstrap if the cache needs to be // regenerated), rather than drush_parse_command() which always requires a // site bootstrap. $arguments = drush_get_arguments(); $set_command_name = NULL; if (isset($arguments[0]) && in_array($arguments[0] . ' ', drush_complete_get('command-names'))) { $set_command_name = $arguments[0]; } // We unset the command if it is "help" but that is not explicitly found in // args, since Drush sets the command to "help" if no command is specified, // which prevents completion of global options. if ($set_command_name == 'help' && !array_search('help', $argv)) { $set_command_name = NULL; } // Determine the word we are trying to complete, and if it is an option. $last_word = end($argv); $word_is_option = FALSE; if (!empty($last_word) && $last_word[0] == '-') { $word_is_option = TRUE; $last_word = ltrim($last_word, '-'); } $completions = array(); if (!$set_command_name) { // We have no command yet. if ($word_is_option) { // Include global option completions. $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options'))); } else { if (empty($set_sitealias_name)) { // Include site alias completions. $completions += drush_complete_match($last_word, drush_complete_get('site-aliases')); } // Include command completions. $completions += drush_complete_match($last_word, drush_complete_get('command-names')); } } else { if ($last_word == $set_command_name) { // The user just typed a valid command name, but we still do command // completion, as there may be other commands that start with the detected // command (e.g. "make" is a valid command, but so is "make-test"). // If there is only the single matching command, this will include in the // completion list so they get a space inserted, confirming it is valid. $completions += drush_complete_match($last_word, drush_complete_get('command-names')); } else if ($word_is_option) { // Include command option completions. $completions += drush_hyphenate_options(drush_complete_match($last_word, drush_complete_get('options', $set_command_name))); } else { // Include command argument completions. $argument_completion = drush_complete_get('arguments', $set_command_name); if (isset($argument_completion['values'])) { $completions += drush_complete_match($last_word, $argument_completion['values']); } if (isset($argument_completion['files'])) { $completions += drush_complete_match_file($last_word, $argument_completion['files']); } } } if (!empty($completions)) { sort($completions); return implode("\n", $completions); } return TRUE; } /** * This function resets the raw arguments so that Drush can parse the command as * if it was run directly. The shell complete command passes the * full command line as an argument, and the --early and --complete-debug * options have to come before that, and the "drush" bash script will add a * --php option on the end, so we end up with something like this: * * /path/to/drush.php --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... --php=/usr/bin/php * * Note that "drush" occurs twice, and also that the second occurrence could be * an alias, so we can't easily use it as to detect the start of the actual * command. Hence our approach is to remove the initial "drush" and then any * options directly following that - what remains is then the command we need * to complete - i.e.: * * drush [@alias] [command]... * * Note that if completion is initiated following a space an empty argument is * added to argv. So in that case argv looks something like this: * array ( * '0' => '/path/to/drush.php', * '1' => '--early=includes/complete.inc', * '2' => 'drush', * '3' => 'topic', * '4' => '', * '5' => '--php=/usr/bin/php', * ); * * @return $args * Array of arguments (argv), excluding the initial command and options * associated with the complete call. * array ( * '0' => 'drush', * '1' => 'topic', * '2' => '', * ); */ function drush_complete_process_argv() { $argv = drush_get_context('argv'); // Remove the first argument, which will be the "drush" command. array_shift($argv); while (substr($arg = array_shift($argv), 0, 2) == '--') { // We remove all options, until we get to a non option, which // marks the start of the actual command we are trying to complete. } // Replace the initial argument. array_unshift($argv, $arg); // Remove the --php option at the end if exists (added by the "drush" shell // script that is called when completion is requested). if (substr(end($argv), 0, 6) == '--php=') { array_pop($argv); } drush_set_context('argv', $argv); drush_set_command(NULL); // Reparse arguments, site alias, and command. drush_parse_args(); // Ensure the base environment is configures, so tests look in the correct // places. _drush_bootstrap_base_environment(); // Check for and record any site alias. drush_sitealias_check_arg(); drush_sitealias_check_site_env(); // Return the new argv for easy reference. return $argv; } /** * Retrieves the appropriate list of candidate completions, then filters this * list using the last word that we are trying to complete. * * @param string $last_word * The last word in the argument list (i.e. the subject of completion). * @param array $values * Array of possible completion values to filter. * * @return array * Array of candidate completions that start with the same characters as the * last word. If the last word is empty, return all candidates. */ function drush_complete_match($last_word, $values) { // Using preg_grep appears to be faster that strpos with array_filter/loop. return preg_grep('/^' . preg_quote($last_word, '/') . '/', $values); } /** * Retrieves the appropriate list of candidate file/directory completions, * filtered by the last word that we are trying to complete. * * @param string $last_word * The last word in the argument list (i.e. the subject of completion). * @param array $files * Array of file specs, each with a pattern and flags subarray. * * @return array * Array of candidate file/directory completions that start with the same * characters as the last word. If the last word is empty, return all * candidates. */ function drush_complete_match_file($last_word, $files) { $return = array(); $firstchar = ''; $full_paths = TRUE; if (isset($last_word) && $last_word[0] == '~') { // Complete does not do tilde expansion, so we do it here. $parts = explode('/', $last_word); // We shell out (unquoted) to expand the tilde. drush_shell_exec('echo ' . $parts[0]); $output = drush_shell_exec_output(); $parts[0] = $output[0]; $last_word = implode('/', $parts); } foreach ($files as $spec) { // We always include GLOB_MARK, as an easy way to detect directories. $flags = GLOB_MARK; if (isset($spec['flags'])) { $flags = $spec['flags'] | GLOB_MARK; } $listing = glob($last_word . $spec['pattern'], $flags); foreach ($listing as $item) { // Detect if the initial characters of the file/dirs to be listing differ. // If they do, we return a list of just their names. If they all have the // same first character we return full paths, to prevent the shell // replacing the current path with just the matching character(s). $char = $item[strrpos($last_word, '/') + 1]; if (empty($firstchar)) { $firstchar = $char; } else if ($firstchar !== $char) { $full_paths = FALSE; } $return[] = $item; } } // If we don't need to return full paths, shorten them appropriately. if ($full_paths == FALSE) { foreach ($return as $id => $item) { $return[$id] = substr($return[$id], strrpos($last_word, '/') + 1); } } // If we are returning a single item (which will become part of the final // command), we need to use the full path, and we need to escape it // appropriately. if (count($return) == 1) { // Escape common shell metacharacters (we don't use escapeshellarg as it // single quotes everything, even when unnecessary). $item = preg_replace('/[ |&;()<>]/', "\\\\$0", $item); if (substr($item, -1) !== '/') { // Insert a space after files, since the argument is complete. $item = $item . ' '; } $return = array($item); } return $return; } /** * Simple helper function to ensure options are properly hyphenated before we * return them to the user (we match against the non-hyphenated versions * internally). * * @param array $options * Array of unhyphenated option names. * * @return array * Array of hyphenated option names. */ function drush_hyphenate_options($options) { foreach ($options as $key => $option) { $options[$key] = '--' . ltrim($option, '--'); } return $options; } /** * Retrieves from cache, or generates a listing of completion candidates of a * specific type (and optionally, command). * * @param string $type * String indicating type of completions to return. * See drush_complete_rebuild() for possible keys. * @param string $command * An optional command name if command specific completion is needed. * * @return array * List of candidate completions. */ function drush_complete_get($type, $command = NULL) { if (empty($command)) { // Retrieve global items from a non-command specific cache, or rebuild cache // if needed. $cache = drush_cache_get(drush_complete_cache_cid($type), 'complete'); if (isset($cache->data)) { return $cache->data; } $complete = drush_complete_rebuild(); return $complete[$type]; } // Retrieve items from a command specific cache. $cache = drush_cache_get(drush_complete_cache_cid($type, $command), 'complete'); if (isset($cache->data)) { return $cache->data; } // Build argument cache - built only on demand. if ($type == 'arguments') { return drush_complete_rebuild_arguments($command); } // Rebuild cache of general command specific items. $complete = drush_complete_rebuild(); if (!empty($complete['commands'][$command][$type])) { return $complete['commands'][$command][$type]; } return array(); } /** * Rebuild and cache completions for everything except command arguments. * * @return array * Structured array of completion types, commands and candidate completions. */ function drush_complete_rebuild() { $complete = array(); // Bootstrap to the site level (if possible) - commands may need to check // the bootstrap level, and perhaps bootstrap higher in extraordinary cases. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); $commands = drush_get_commands(); foreach ($commands as $command_name => $command) { // Add command options and suboptions. $options = array_keys($command['options']); foreach ($command['sub-options'] as $option => $sub_options) { $options = array_merge($options, array_keys($sub_options)); } $complete['commands'][$command_name]['options'] = $options; } // We treat shell aliases as commands for the purposes of completion. $complete['command-names'] = array_merge(array_keys($commands), array_keys(drush_get_context('shell-aliases', array()))); $site_aliases = _drush_sitealias_all_list(); // TODO: Figure out where this dummy @0 alias is introduced. unset($site_aliases['@0']); $complete['site-aliases'] = array_keys($site_aliases); $complete['options'] = array_keys(drush_get_global_options()); // We add a space following all completes. Eventually there may be some // items (e.g. options that we know need values) where we don't add a space. array_walk_recursive($complete, 'drush_complete_trailing_space'); drush_complete_cache_set($complete); return $complete; } /** * Helper callback function that adds a trailing space to completes in an array. */ function drush_complete_trailing_space(&$item, $key) { if (!is_array($item)) { $item = (string)$item . ' '; } } /** * Rebuild and cache completions for command arguments. * * @param string $command * A specific command to retrieve and cache arguments for. * * @return array * Structured array of candidate completion arguments, keyed by the command. */ function drush_complete_rebuild_arguments($command) { // Bootstrap to the site level (if possible) - commands may need to check // the bootstrap level, and perhaps bootstrap higher in extraordinary cases. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); $commands = drush_get_commands(); $hook = str_replace("-", "_", $commands[$command]['command-hook']); $result = drush_command_invoke_all($hook . '_complete'); if (isset($result['values'])) { // We add a space following all completes. Eventually there may be some // items (e.g. comma separated arguments) where we don't add a space. array_walk($result['values'], 'drush_complete_trailing_space'); } $complete = array( 'commands' => array( $command => array( 'arguments' => $result, ) ) ); drush_complete_cache_set($complete); return $complete['commands'][$command]['arguments']; } /** * Stores caches for completions. * * @param $complete * A structured array of completions, keyed by type, including a 'commands' * type that contains all commands with command specific completions keyed by * type. The array does not need to include all types - used by * drush_complete_rebuild_arguments(). */ function drush_complete_cache_set($complete) { foreach ($complete as $type => $values) { if ($type == 'commands') { foreach ($values as $command_name => $command) { foreach ($command as $command_type => $command_values) { drush_cache_set(drush_complete_cache_cid($command_type, $command_name), $command_values, 'complete', DRUSH_CACHE_TEMPORARY); } } } else { drush_cache_set(drush_complete_cache_cid($type), $values, 'complete', DRUSH_CACHE_TEMPORARY); } } } /** * Generate a cache id. * * @param $type * The completion type. * @param $command * The command name (optional), if completions are command specific. * * @return string * Cache id. */ function drush_complete_cache_cid($type, $command = NULL) { // For per-site caches, we include the site root and uri/path in the cache id // hash. These are quick to determine, and prevents a bootstrap to site just // to get a validated root and URI. Because these are not validated, there is // the possibility of cache misses/ but they should be rare, since sites are // normally referred to the same way (e.g. a site alias, or using the current // directory), at least within a single command completion session. // We also static cache them, since we may get differing results after // bootstrap, which prevents the caches from being found on the next call. static $root, $site; if (empty($root)) { $root = drush_get_option(array('r', 'root'), drush_locate_root()); $site = drush_get_option(array('l', 'uri'), drush_site_path()); } return drush_get_cid('complete', array(), array($type, $command, $root, $site)); } drush-5.10.0/includes/context.inc000066400000000000000000000543641222105546100167210ustar00rootroot00000000000000 realpath($config), '!context' => $context)), 'bootstrap'); $ret = @include_once($config); if ($ret === FALSE) { drush_log(dt('Cannot open drushrc "!config", ignoring.', array('!config' => realpath($config))), 'warning'); return FALSE; } if (!empty($options) || !empty($aliases) || !empty($command_specific) || !empty($override)) { $options = array_merge(drush_get_context($context), $options); $options['config-file'] = realpath($config); //$options['site-aliases'] = array_merge(isset($aliases) ? $aliases : array(), isset($options['site-aliases']) ? $options['site-aliases'] : array()); unset($options['site-aliases']); $options['command-specific'] = array_merge(isset($command_specific) ? $command_specific : array(), isset($options['command-specific']) ? $options['command-specific'] : array()); drush_set_config_options($context, $options, $override); } } } } function drush_set_config_options($context, $options, $override = array()) { // Copy 'config-file' into 'context-path', converting to an array to hold multiple values if necessary if (isset($options['config-file'])) { if (isset($options['context-path'])) { $options['context-path'] = array_merge(array($options['config-file']), is_array($options['context-path']) ? $options['context-path'] : array($options['context-path'])); } else { $options['context-path'] = $options['config-file']; } } // Take out $aliases and $command_specific options drush_set_config_special_contexts($options); drush_set_context($context, $options); } /** * For all global options with a short form, convert all options in the option * array that use the short form into the long form. */ function drush_expand_short_form_options(&$options) { foreach (drush_get_global_options() as $name => $info) { if (is_array($info)) { // For any option with a short form, check to see if the short form was set in the // options. If it was, then rename it to its long form. if (array_key_exists('short-form', $info) && array_key_exists($info['short-form'], $options)) { if (!array_key_exists($name, $options) || !array_key_exists('merge-pathlist', $info)) { $options[$name] = $options[$info['short-form']]; } else { $options[$name] = array_merge((array)$options[$name], (array)$options[$info['short-form']]); } unset($options[$info['short-form']]); } } } } /** * There are certain options such as 'site-aliases' and 'command-specific' * that must be merged together if defined in multiple drush configuration * files. If we did not do this merge, then the last configuration file * that defined any of these properties would overwrite all of the options * that came before in previously-loaded configuration files. We place * all of them into their own context so that this does not happen. */ function drush_set_config_special_contexts(&$options) { if (isset($options) && is_array($options)) { $has_command_specific = array_key_exists('command-specific', $options); // Change the keys of the site aliases from 'alias' to '@alias' if (array_key_exists('site-aliases', $options)) { $user_aliases = $options['site-aliases']; $options['site-aliases'] = array(); foreach ($user_aliases as $alias_name => $alias_value) { if (substr($alias_name,0,1) != '@') { $alias_name = "@$alias_name"; } $options['site-aliases'][$alias_name] = $alias_value; } } // Expand -s into --simulate, etc. drush_expand_short_form_options($options); foreach (drush_get_global_options() as $name => $info) { if (is_array($info)) { // For any global option with the 'merge-pathlist' or 'merge-associative' flag, set its // value in the specified context. The option is 'merged' because we // load $options with the value from the context prior to including the // configuration file. If the configuration file sets $option['special'][] = 'value', // then the configuration will be merged. $option['special'] = array(...), on the // other hand, will replace rather than merge the values together. if ((array_key_exists($name, $options)) && (array_key_exists('merge', $info) || (array_key_exists('merge-pathlist', $info) || array_key_exists('merge-associative', $info)))) { $context = array_key_exists('context', $info) ? $info['context'] : $name; $cache =& drush_get_context($context); $value = $options[$name]; if (!is_array($value) && array_key_exists('merge-pathlist', $info)) { $value = explode(PATH_SEPARATOR, $value); } if (array_key_exists('merge-associative', $info)) { foreach ($value as $subkey => $subvalue) { $cache[$subkey] = array_merge(isset($cache[$subkey]) ? $cache[$subkey] : array(), $subvalue); } } else { $cache = array_unique(array_merge($cache, $value)); } // Once we have moved the option to its special context, we // can remove it from its option context -- unless 'propagate-cli-value' // is set, in which case we need to let it stick around in options // in case it is needed in backend invoke. if (!array_key_exists('propagate-cli-value', $info)) { unset($options[$name]); } } } } // If command-specific options were set and if we already have // a command, then apply the command-specific options immediately. if ($has_command_specific) { drush_command_default_options(); } } } /** * Set a specific context. * * @param context * Any of the default defined contexts. * @param value * The value to store in the context * * @return * An associative array of the settings specified in the request context. */ function drush_set_context($context, $value) { $cache =& drush_get_context($context); $cache = $value; return $value; } /** * Return a specific context, or the whole context cache * * This function provides a storage mechanism for any information * the currently running process might need to communicate. * * This avoids the use of globals, and constants. * * Functions that operate on the context cache, can retrieve a reference * to the context cache using : * $cache = &drush_get_context($context); * * This is a private function, because it is meant as an internal * generalized API for writing static cache functions, not as a general * purpose function to be used inside commands. * * Code that modifies the reference directly might have unexpected consequences, * such as modifying the arguments after they have already been parsed and dispatched * to the callbacks. * * @param context * Optional. Any of the default defined contexts. * * @return * If context is not supplied, the entire context cache will be returned. * Otherwise only the requested context will be returned. * If the context does not exist yet, it will be initialized to an empty array. */ function &drush_get_context($context = NULL, $default = NULL) { static $cache = array(); if (!is_null($context)) { if (!isset($cache[$context])) { $default = is_null($default) ? array() : $default; $cache[$context] = $default; } return $cache[$context]; } return $cache; } /** * Set the arguments passed to the drush.php script. * * This function will set the 'arguments' context of the current running script. * * When initially called by drush_parse_args, the entire list of arguments will * be populated. Once the command is dispatched, this will be set to only the remaining * arguments to the command (i.e. the command name is removed). * * @param arguments * Command line arguments, as an array. */ function drush_set_arguments($arguments) { drush_set_context('arguments', $arguments); } /** * Gets the command line arguments passed to Drush. * * @return array * An indexed array of arguments. Until Drush has dispatched the command, the * array will include the command name as the first element. After that point * the array will not include the command name. * * @see drush_set_arguments() */ function drush_get_arguments() { return drush_get_context('arguments'); } /** * Set the command being executed. * * Drush_dispatch will set the correct command based on it's * matching of the script arguments retrieved from drush_get_arguments * to the implemented commands specified by drush_get_commands. * * @param * A numerically indexed array of command components. */ function drush_set_command($command) { drush_set_context('command', $command); } /** * Return the command being executed. * * */ function drush_get_command() { return drush_get_context('command'); } /** * Get the value for an option. * * If the first argument is an array, then it checks whether one of the options * exists and return the value of the first one found. Useful for allowing both * -h and --host-name * * @param option * The name of the option to get * @param default * Optional. The value to return if the option has not been set * @param context * Optional. The context to check for the option. If this is set, only this context will be searched. */ function drush_get_option($option, $default = NULL, $context = NULL) { $value = NULL; if ($context) { // We have a definite context to check for the presence of an option. $value = _drush_get_option($option, drush_get_context($context)); } else { // We are not checking a specific context, so check them in a predefined order of precedence. $contexts = drush_context_names(); foreach ($contexts as $context) { $value = _drush_get_option($option, drush_get_context($context)); if ($value !== NULL) { return $value; } } } if ($value !== NULL) { return $value; } return $default; } /** * Get the value for an option and return it as a list. If the * option in question is passed on the command line, its value should * be a comma-separated list (e.g. --flag=1,2,3). If the option * was set in a drushrc.php file, then its value may be either a * comma-separated list or an array of values (e.g. $option['flag'] = array('1', '2', '3')). * * @param option * The name of the option to get * @param default * Optional. The value to return if the option has not been set * @param context * Optional. The context to check for the option. If this is set, only this context will be searched. */ function drush_get_option_list($option, $default = array(), $context = NULL) { $result = drush_get_option($option, $default, $context); if (!is_array($result)) { $result = explode(',', $result); } return $result; } /** * Get the value for an option, but first checks the provided option overrides. * * The feature of drush_get_option that allows a list of option names * to be passed in an array is NOT supported. * * @param option_overrides * An array to check for values before calling drush_get_option. * @param option * The name of the option to get. * @param default * Optional. The value to return if the option has not been set. * @param context * Optional. The context to check for the option. If this is set, only this context will be searched. * */ function drush_get_option_override($option_overrides, $option, $default = NULL, $context = NULL) { return drush_sitealias_get_option($option_overrides, $option, $default, '', $context); } /** * Get an option out of the specified alias. If it has not been * set in the alias, then get it via drush_get_option. * * @param site_alias_record * An array of options for an alias record. * @param option * The name of the option to get. * @param default * Optional. The value to return if the option does not exist in the site record and has not been set in a context. * @param context * Optional. The context to check for the option. If this is set, only this context will be searched. */ function drush_sitealias_get_option($site_alias_record, $option, $default = NULL, $prefix = '', $context = NULL) { if (is_array($site_alias_record) && array_key_exists($option, $site_alias_record)) { return $site_alias_record[$option]; } else { return drush_get_option($prefix . $option, $default, $context); } } /** * Get all of the values for an option in every context. * * @param option * The name of the option to get * @return * An array whose key is the context name and value is * the specific value for the option in that context. */ function drush_get_context_options($option, $flatten = FALSE) { $result = array(); $contexts = drush_context_names(); foreach ($contexts as $context) { $value = _drush_get_option($option, drush_get_context($context)); if ($value !== NULL) { if ($flatten && is_array($value)) { $result = array_merge($value, $result); } else { $result[$context] = $value; } } } return $result; } /** * Retrieves a collapsed list of all options. */ function drush_get_merged_options() { $contexts = drush_context_names(); $cache = drush_get_context(); $result = array(); foreach (array_reverse($contexts) as $context) { if (array_key_exists($context, $cache)) { $result = array_merge($result, $cache[$context]); } } return $result; } /** * Retrieves a collapsed list of all options * with a specified prefix. */ function drush_get_merged_prefixed_options($prefix) { $merged_options = drush_get_merged_options(); $result = array(); foreach ($merged_options as $key => $value) { if ($prefix == substr($key, 0, strlen($prefix))) { $result[substr($key, strlen($prefix))] = $value; } } return $result; } /** * Helper function to recurse through possible option names */ function _drush_get_option($option, $context) { if (is_array($option)) { foreach ($option as $current) { if (array_key_exists($current, $context)) { return $context[$current]; } } } elseif (array_key_exists($option, $context)) { return $context[$option]; } return NULL; } /** * Set an option in one of the option contexts. * * @param option * The option to set. * @param value * The value to set it to. * @param context * Optional. Which context to set it in. * @return * The value parameter. This allows for neater code such as * $myvalue = drush_set_option('http_host', $_SERVER['HTTP_HOST']); * Without having to constantly type out the value parameter. */ function drush_set_option($option, $value, $context = 'process') { $cache =& drush_get_context($context); $cache[$option] = $value; return $value; } /** * A small helper function to set the value in the default context */ function drush_set_default($option, $value) { return drush_set_option($option, $value, 'default'); } /** * Remove a setting from a specific context. * * @param * Option to be unset * @param * Context in which to unset the value in. */ function drush_unset_option($option, $context = NULL) { if ($context != NULL) { $cache =& drush_get_context($context); if (array_key_exists($option, $cache)) { unset($cache[$option]); } } else { $contexts = drush_context_names(); foreach ($contexts as $context) { drush_unset_option($option, $context); } } } /** * Save the settings in a specific context to the applicable configuration file * This is useful is you want certain settings to be available automatically the next time a command is executed. * * @param $context * The context to save */ function drush_save_config($context) { $filename = _drush_config_file($context); if (is_array($filename)) { $filename = $filename[0]; } if ($filename) { $cache = drush_get_context($context); $fp = fopen($filename, "w+"); if (!$fp) { return drush_set_error('DRUSH_PERM_ERROR', dt('Drushrc (!filename) could not be written', array('!filename' => $filename))); } else { fwrite($fp, " $value) { $line = "\n\$options['$key'] = ". var_export($value, TRUE) .';'; fwrite($fp, $line); } fwrite($fp, "\n"); fclose($fp); drush_log(dt('Drushrc file (!filename) was written successfully', array('!filename' => $filename))); return TRUE; } } return FALSE; } drush-5.10.0/includes/dbtng.inc000066400000000000000000000122561222105546100163250ustar00rootroot00000000000000 $data) { if (is_array($data)) { $new_keys = array(); // $data can't have keys that are a prefix of other keys to // prevent a corrupted result in the below calls to str_replace(). // To avoid this we will use a zero padded indexed array of the values of $data. $pad_length = strlen((string)count(array_values($data))); foreach (array_values($data) as $i => $value) { if (!is_numeric($value)) { $value = "'".$value."'"; } $new_keys[$key . '_' . str_pad($i, $pad_length, '0', STR_PAD_LEFT)] = $value; } $where = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $where); unset($args[$key]); $args += $new_keys; } else if (!is_numeric($data)) { $args[$key] = "'".$data."'"; } } foreach ($args as $key => $data) { $where = str_replace($key, $data, $where); } return $where; } /** * A db_select() that works for any version of Drupal. * * @param $table * String. The table to operate on. * @param $fields * Array or string. Fields affected in this operation. Valid string values are '*' or a single column name. * @param $where * String. WHERE snippet for the operation. It uses named placeholders. see @_drush_replace_query_placeholders() * @param $args * Array. Arguments for the WHERE snippet. * @param $start * Int. Value for OFFSET. * @param $length * Int. Value for LIMIT. * @param $order_by_field * String. Database column to order by. * @param $order_by_direction * ('ASC', 'DESC'). Ordering direction. * @return * A database resource. */ function drush_db_select($table, $fields = '*', $where = NULL, $args = NULL, $start = NULL, $length = NULL, $order_by_field = NULL, $order_by_direction = 'ASC') { if (drush_drupal_major_version() >= 7) { if (!is_array($fields)) { if ($fields == '*') { $fields = array(); } else { $fields = array($fields); } } $query = db_select($table, $table) ->fields($table, $fields); if (!empty($where)) { $query = $query->where($where, $args); } if (!is_null($order_by_field)) { $query = $query->orderBy($order_by_field, $order_by_direction); } if (!is_null($length)) { $query = $query->range($start, $length); } return $query->execute(); } else { if (is_array($fields)) { $fields = implode(', ', $fields); } $query = "SELECT $fields FROM {{$table}}"; if (!empty($where)) { $where = _drush_replace_query_placeholders($where, $args); $query .= " WHERE ".$where; } if (!is_null($order_by_field)) { $query .= " ORDER BY $order_by_field $order_by_direction"; } if (!is_null($length)) { $db_spec = _drush_sql_get_db_spec(); $db_scheme = _drush_sql_get_scheme($db_spec); if ($db_scheme == 'oracle') return db_query_range($query, $start, $length); else { $limit = " LIMIT $length"; if (!is_null($start)) { $limit .= " OFFSET $start"; } $query .= $limit; } } return db_query($query, $args); } } /** * A db_delete() that works for any version of Drupal. * * @param $table * String. The table to operate on. * @param $where * String. WHERE snippet for the operation. It uses named placeholders. see @_drush_replace_query_placeholders() * @param $args * Array. Arguments for the WHERE snippet. * @return * Affected rows (except on D7+mysql without a WHERE clause - returns TRUE) or FALSE. */ function drush_db_delete($table, $where = NULL, $args = NULL) { if (drush_drupal_major_version() >= 7) { if (!empty($where)) { $query = db_delete($table)->where($where, $args); return $query->execute(); } else { return db_truncate($table)->execute(); } } else { $query = "DELETE FROM {{$table}}"; if (!empty($where)) { $where = _drush_replace_query_placeholders($where, $args); $query .= ' WHERE '.$where; } if (!db_query($query, $args)) { return FALSE; } return db_affected_rows(); } } /** * A db_result() that works consistently for any version of Drupal. * * @param * A Database result object. */ function drush_db_result($result) { switch (drush_drupal_major_version()) { case 6: return db_result($result); case 7: default: return $result->fetchField(); } } /** * A db_fetch_object() that works for any version of Drupal. * * @param * A Database result object. */ function drush_db_fetch_object($result) { return drush_drupal_major_version() >= 7 ? $result->fetchObject() : db_fetch_object($result); } /** * @} End of "defgroup dbfunctions". */ drush-5.10.0/includes/drupal.inc000066400000000000000000000167431222105546100165230ustar00rootroot00000000000000= 6 && $log_entry['severity'] <= 2) { $severity = 'error'; } else { drush_include_engine('drupal', 'environment'); $levels = core_watchdog_severity_levels(); $severity = $levels[$log_entry['severity']]; } // Format the message. if (is_array($log_entry['variables'])) { $message = strtr($log_entry['message'], $log_entry['variables']); } else { $message = $log_entry['message']; } // decode_entities() only loaded after FULL bootstrap. if (function_exists('decode_entities')) { $message = decode_entities($message); } $message = strip_tags($message); // Log or print or ignore. Just printing saves memory but thats rarely needed. switch (drush_get_option('watchdog', 'log')) { case 'log': drush_log('WD '. $log_entry['type'] . ': ' . $message, $severity); break; case 'print': // Disable in backend mode since it logs output and the goal is to conserve memory. // @see _drush_bootstrap_drush(). if (ob_get_length() === FALSE) { drush_print('WD '. $severity . ' ' . $log_entry['type'] . ': ' . $message); } break; default: // Do nothing. } } /** * Log the return value of Drupal hook_update_n functions. * * This is used during install and update to log the output * of the update process to the logging system. */ function _drush_log_update_sql($ret) { if (sizeof($ret)) { foreach ($ret as $info) { if (is_array($info)) { if (!$info['success']) { drush_set_error('DRUPAL_UPDATE_FAILED', $info['query']); } else { drush_log($info['query'], ($info['success']) ? 'success' : 'error'); } } } } } function drush_find_profiles($drupal_root , $key = 'name') { return drush_scan_directory($drupal_root . '/profiles', "/.*\.profile$/", array('.', '..', 'CVS', 'tests'), 0, 2, $key); } /** * Log the given user in to a bootstrapped Drupal site. * * @param mixed * Numeric user id or user name. * * @return boolean * TRUE if user was logged in, otherwise FALSE. */ function drush_drupal_login($drush_user) { global $user; if (drush_drupal_major_version() >= 7) { $user = is_numeric($drush_user) ? user_load($drush_user) : user_load_by_name($drush_user); } else { $user = user_load(is_numeric($drush_user) ? array('uid' => $drush_user) : array('name' => $drush_user)); } if (empty($user)) { if (is_numeric($drush_user)) { $message = dt('Could not login with user ID #!user.', array('!user' => $drush_user)); if ($drush_user === 0) { $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')); } } else { $message = dt('Could not login with user account `!user\'.', array('!user' => $drush_user)); } return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message); } else { $name = $user->name ? $user->name : variable_get('anonymous', t('Anonymous')); drush_log(dt('Successfully logged into Drupal as !name', array('!name' => $name . " (uid=$user->uid)")), 'bootstrap'); } return TRUE; } /** * Parse Drupal info file format. * * Copied with modifications from includes/common.inc. * * @see drupal_parse_info_file */ function drush_drupal_parse_info_file($filename) { if (!file_exists($filename)) { return array(); } $data = file_get_contents($filename); return _drush_drupal_parse_info_file($data); } /** * Parse the info file. */ function _drush_drupal_parse_info_file($data) { if (!$data) { return FALSE; } if (preg_match_all(' @^\s* # Start at the beginning of a line, ignoring leading whitespace ((?: [^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets, \[[^\[\]]*\] # unless they are balanced and not nested )+?) \s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space) (?: ("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes ([^\r\n]*?) # Non-quoted string )\s*$ # Stop at the next end of a line, ignoring trailing whitespace @msx', $data, $matches, PREG_SET_ORDER)) { $info = array(); foreach ($matches as $match) { // Fetch the key and value string. $i = 0; foreach (array('key', 'value1', 'value2', 'value3') as $var) { $$var = isset($match[++$i]) ? $match[$i] : ''; } $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3; // Parse array syntax. $keys = preg_split('/\]?\[/', rtrim($key, ']')); $last = array_pop($keys); $parent = &$info; // Create nested arrays. foreach ($keys as $key) { if ($key == '') { $key = count($parent); } if (!isset($parent[$key]) || !is_array($parent[$key])) { $parent[$key] = array(); } $parent = &$parent[$key]; } // Handle PHP constants. if (defined($value)) { $value = constant($value); } // Insert actual value. if ($last == '') { $last = count($parent); } $parent[$last] = $value; } return $info; } return FALSE; } drush-5.10.0/includes/drush.inc000066400000000000000000002314751222105546100163620ustar00rootroot00000000000000 $file))); include_once($file); return TRUE; } $file = sprintf("%s/%s.%s", $path, $name, $extension); if (file_exists($file)) { //drush_log(dt('Including non-version specific file : @file', array('@file' => $file))); include_once($file); return TRUE; } } /** * Obtain all engine types info and normalize with defaults. * * @see hook_drush_engine_type_info(). */ function drush_get_engine_types_info() { $info = drush_command_invoke_all('drush_engine_type_info'); foreach ($info as $type => $data) { $info[$type] += array( 'description' => '', 'option' => FALSE, 'default' => NULL, 'options' => array(), 'add-options-to-command' => FALSE, ); } return $info; } /** * Return a structured array of engines of a specific type. * * Engines are pluggable subsystems. Each engine of a specific type will * implement the same set of API functions and perform the same high-level * task using a different backend or approach. * * This function/hook is useful when you have a selection of several mutually * exclusive options to present to a user to select from. * * Other commands are able to extend this list and provide their own engines. * The hook can return useful information to help users decide which engine * they need, such as description or list of available engine options. * * The engine path element will automatically default to a subdirectory (within * the directory of the commandfile that implemented the hook) with the name of * the type of engine - e.g. an engine "wget" of type "handler" provided by * the "pm" commandfile would automatically be found if the file * "pm/handler/wget.inc" exists and a specific path is not provided. * * @param $engine_type * The type of engine. * * @return * A structured array of engines. */ function drush_get_engines($engine_type) { $info = drush_get_engine_types_info(); if (!isset($info[$engine_type])) { return drush_set_error('DRUSH_UNKNOWN_ENGINE_TYPE', dt('Unknown engine type !engine_type', array('!engine_type' => $engine_type))); } $engines = array( 'info' => $info[$engine_type], 'engines' => array(), ); $list = drush_commandfile_list(); $hook = 'drush_engine_' . $engine_type; foreach ($list as $commandfile => $path) { if (drush_command_hook($commandfile, $hook)) { $function = $commandfile . '_' . $hook; $result = $function(); foreach ($result as $key => $engine) { // Add some defaults $engine += array( 'commandfile' => $commandfile, // Engines by default live in a subdirectory of the commandfile that // declared them, named as per the type of engine they are. 'path' => sprintf("%s/%s", dirname($path), $engine_type), ); $engines['engines'][$key] = $engine; } } } return $engines; } /** * Include the engine code for a specific named engine of a certain type. * * If the engine type has implemented hook_drush_engine_$type the path to the * engine specified in the array will be used. * * If a class named in the form drush_$type_$engine exists, it will be an * object of that class will be created and returned. * * If you don't need to present any user options for selecting the engine * (which is common if the selection is implied by the running environment) * and you don't need to allow other modules to define their own engines you can * simply pass the $path to the directory where the engines are, and the * appropriate one will be included. * * Unlike drush_include this function will set errors if the requested engine * cannot be found. * * @param $type * The type of engine. * @param $engine * The key for the engine to be included. * @param $version * The version of the engine to be included - defaults to the current Drupal core * major version. * @param $path * A path to include from, if the engine has no corresponding * hook_drush_engine_$type item path. * @return TRUE or instanced object of available class on success. FALSE on fail. */ function drush_include_engine($type, $engine, $version = NULL, $path = NULL) { $engine_info = drush_get_engines($type); if (!$path && isset($engine_info['engines'][$engine])) { $path = $engine_info['engines'][$engine]['path']; } if (!$path) { return drush_set_error('DRUSH_ENGINE INCLUDE_NO_PATH', dt('No path was set for including the !type engine !engine.', array('!type' => $type, '!engine' => $engine))); } if (drush_include($path, $engine, $version)) { $class = 'drush_' . $type . '_' . $engine; if (class_exists($class)) { return new $class(); } return TRUE; } return drush_set_error('DRUSH_ENGINE INCLUDE_FAILED', dt('Unable to include the !type engine !engine from !path.' , array('!path' => $path, '!type' => $type, '!engine' => $engine))); } /** * Check to see if a newer version of drush is available * * @return * TRUE - A new version is available. * FALSE - Error. * NULL - No release available. */ function drush_check_self_update() { $explicit = FALSE; $update = FALSE; $error = ""; // Don't check unless we have a datestamp in drush.info $drush_info = drush_read_drush_info(); if (($drush_info === FALSE) || (!array_key_exists('datestamp', $drush_info))) { drush_log(dt('Cannot determine release date for drush'), 'notice'); return FALSE; } $is_dev = FALSE; // Get release info for drush. drush_include_engine('release_info', 'updatexml'); $request = pm_parse_project_version(array('drush')); $info = release_info_get_releases($request); // Check for newer releases based on the datestamp. // We add 60 seconds to the drush.info date because of a drupal.org WTF. See http://drupal.org/node/1019356. $version_date = $drush_info['datestamp'] + 60; $newer_version = FALSE; foreach ($info['drush']['releases'] as $version => $release_info) { // We deliberately skip any dev releases unless the current release is a dev release. if ((!array_key_exists('version_extra', $release_info) || ($release_info['version_extra'] != 'dev'))) { if ($release_info['date'] > $version_date) { $newer_version = $release_info['version']; $version_date = $release_info['date']; $is_dev = isset($release_info['version_extra']) && $release_info['version_extra'] == 'dev'; if ($is_dev) { $newer_version .= " (" . date('Y-M-d', $version_date) . ")"; } } } } if ($newer_version) { drush_print(dt('A newer version of drush, !version, is available. You are currently running drush version !currentversion. The update process depends on how you installed drush. Some common update commands are: `pear upgrade drush/drush`, `git pull`, `drush dl drush --destination=[/path/to/drush]`.' . "\n", array('!version' => $newer_version, '!currentversion' => DRUSH_VERSION))); return TRUE; } else { drush_log(dt("drush self-update check: drush !version is up-to-date.", array('!version' => DRUSH_VERSION)), 'notice'); } return NULL; } /** * Generate an .ini file. used by archive-dump." * * @param array $ini * A two dimensional associative array where top level are sections and * second level are key => value pairs. * * @return string * .ini formatted text. */ function drush_export_ini($ini) { $output = ''; foreach ($ini as $section => $pairs) { if ($section) { $output .= "[$section]\n"; } foreach ($pairs as $k => $v) { if ($v) { $output .= "$k = \"$v\"\n"; } } } return $output; } /** * Generate code friendly to the Drupal .info format from a structured array. * Mostly copied from http://drupalcode.org/viewvc/drupal/contributions/modules/features/features.export.inc. * * @param $info * An array or single value to put in a module's .info file. * * @param boolean $integer_keys * Use integer in keys. * * @param $parents * Array of parent keys (internal use only). * * @return * A code string ready to be written to a module's .info file. */ function drush_export_info($info, $integer_keys = FALSE, $parents = array()) { $output = ''; if (is_array($info)) { foreach ($info as $k => $v) { $child = $parents; $child[] = $k; $output .= drush_export_info($v, $integer_keys, $child); } } else if (!empty($info) && count($parents)) { $line = array_shift($parents); foreach ($parents as $key) { $line .= (!$integer_keys && is_numeric($key)) ? "[]" : "[{$key}]"; } $line .= " = \"{$info}\"\n"; return $line; } return $output; } /** * Convert a csv string, or an array of items which * may contain csv strings, into an array of items. * * @param $args * A simple csv string; e.g. 'a,b,c' * or a simple list of items; e.g. array('a','b','c') * or some combination; e.g. array('a,b','c') or array('a,','b,','c,') * * @returns array * A simple list of items (e.g. array('a','b','c') */ function _convert_csv_to_array($args) { // // Step 1: implode(',',$args) converts from, say, array('a,','b,','c,') to 'a,,b,,c,' // Step 2: explode(',', ...) converts to array('a','','b','','c','') // Step 3: array_filter(...) removes the empty items // return array_filter(explode(',', is_array($args) ? implode(',',$args) : $args)); } /** * Get the available global options. Used by help command. Command files may * modify this list using hook_drush_help_alter(). * * @param boolean $brief * Return a reduced set of important options. Used by help command. * * @return * An associative array containing the option definition as the key, * and a descriptive array for each of the available options. The array * elements for each item are: * * - short-form: The shortcut form for specifying the key on the commandline. * - context: The drush context where the value of this item is cached. Used * by backend invoke to propagate values set in code. * - never-post: If TRUE, backend invoke will never POST this item's value * on STDIN; it will always be sent as a commandline option. * - never-propagate: If TRUE, backend invoke will never pass this item on * to the subcommand being executed. * - local-context-only: Backend invoke will only pass this value on for local calls. * - merge: For options such as $options['shell-aliases'] that consist of an array * of items, make a merged array that contains all of the values specified for * all of the contexts (config files) where the option is defined. The value is stored in * the specified 'context', or in a context named after the option itself if the * context flag is not specified. * IMPORTANT: When the merge flag is used, the option value must be obtained via * drush_get_context('option') rather than drush_get_option('option'). * - merge-pathlist: For options such as --include and --config, make a merged list * of options from all contexts; works like the 'merge' flag, but also handles string * values separated by the PATH_SEPARATOR. * - merge-associative: Like 'merge-pathlist', but key values are preserved. * - propagate-cli-value: Used to tell backend invoke to include the value for * this item as specified on the cli. This can either override 'context' * (e.g., propagate --include from cli value instead of DRUSH_INCLUDE context), * or for an independent global setting (e.g. --user) * - description: The help text for this item. displayed by `drush help`. */ function drush_get_global_options($brief = FALSE) { $options['root'] = array('short-form' => 'r', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => "Drupal root directory to use (default: current directory).", 'example-value' => 'path'); $options['uri'] = array('short-form' => 'l', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => 'URI of the drupal site to use (only needed in multisite environments or when running on an alternate port).', 'example-value' => 'http://example.com:8888'); $options['verbose'] = array('short-form' => 'v', 'context' => 'DRUSH_VERBOSE', 'description' => 'Display extra information about the command.'); $options['debug'] = array('short-form' => 'd', 'context' => 'DRUSH_DEBUG', 'description' => 'Display even more information, including internal messages.'); $options['yes'] = array('short-form' => 'y', 'context' => 'DRUSH_AFFIRMATIVE', 'description' => "Assume 'yes' as answer to all prompts."); $options['no'] = array('short-form' => 'n', 'context' => 'DRUSH_NEGATIVE', 'description' => "Assume 'no' as answer to all prompts."); $options['simulate'] = array('short-form' => 's', 'context' => 'DRUSH_SIMULATE', 'description' => "Simulate all relevant actions (don't actually change the system)."); $options['pipe'] = array('short-form' => 'p', 'description' => "Emit a compact representation of the command for scripting."); $options['help'] = array('short-form' => 'h', 'description' => "This help system."); $options['version'] = array('description' => "Show drush version."); $options['php'] = array('description' => "The absolute path to your PHP intepreter, if not 'php' in the path.", 'example-value' => '/path/to/file', 'never-propagate' => TRUE); $options['interactive'] = array('short-form' => 'ia', 'description' => "Force interactive mode for commands run on multiple targets (e.g. `drush @site1,@site2 cc --ia`)."); if (!$brief) { $options['quiet'] = array('short-form' => 'q', 'description' => 'Suppress non-error messages.'); $options['include'] = array('short-form' => 'i', 'short-has-arg' => TRUE, 'context' => 'DRUSH_INCLUDE', 'local-context-only' => TRUE, 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of additional directory paths to search for drush commands.", 'example-value' => '/path/to/directory'); $options['config'] = array('short-form' => 'c', 'short-has-arg' => TRUE, 'context' => 'DRUSH_CONFIG', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'description' => "Specify an additional config file to load. See example.drushrc.php."); $options['user'] = array('short-form' => 'u', 'short-has-arg' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specify a Drupal user to login with. May be a name or a number.", 'example-value' => 'name_or_number'); $options['backend'] = array('short-form' => 'b', 'never-propagate' => TRUE, 'description' => "Hide all output and return structured data (internal use only)."); $options['invoke'] = array('hidden' => TRUE, 'description' => 'Invoked from a script; skip option verification.'); $options['choice'] = array('description' => "Provide an answer to a multiple-choice prompt.", 'example-value' => 'number'); $options['variables'] = array('description' => "Comma delimited list of name=value pairs. These values take precedence even over settings.php variable overrides.", 'example-value' => 'foo=bar,baz=yaz'); $options['search-depth'] = array('description' => "Control the depth that drush will search for alias files.", 'example-value' => 'number'); $options['ignored-modules'] = array('description' => "Exclude some modules from consideration when searching for drush command files."); $options['no-label'] = array('description' => "Remove the site label that drush includes in multi-site command output(e.g. `drush @site1,@site2 status`)."); $options['nocolor'] = array('context' => 'DRUSH_NOCOLOR', 'propagate-cli-value' => TRUE, 'description' => "Suppress color highlighting on log messages."); $options['show-passwords'] = array('description' => "Show database passwords in commands that display connection information."); $options['show-invoke'] = array('description' => "Show all function names which could have been called for the current command. See drush_invoke()."); $options['watchdog'] = array('description' => "Control logging of Drupal's watchdog() to drush log. Recognized values are 'log', 'print', 'disabled'. Defaults to log. 'print' shows calls to admin but does not add them to the log."); $options['cache-default-class'] = array('description' => "A cache backend class that implements DrushCacheInterface."); $options['cache-class-'] = array('description' => "A cache backend class that implements DrushCacheInterface to use for a specific cache bin."); $options['early'] = array('description' => "Include a file (with relative or full path) and call the drush_early_hook() function (where 'hook' is the filename). The function is called pre-bootstrap and offers an opportunity to alter the drush bootstrap environment or process (returning FALSE from the function will continue the bootstrap), or return output very rapidly (e.g. from caches). See includes/complete.inc for an example."); $options['alias-path'] = array('context' => 'ALIAS_PATH', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specifies the list of paths where drush will search for alias files. Separate paths with ':'."); $options['backup-location'] = array('description' => "Specifies the directory where drush will store backups."); $options['confirm-rollback'] = array('description' => 'Wait for confirmation before doing a rollback when something goes wrong.'); $options['complete-debug'] = array('hidden' => TRUE, 'description' => "Turn on debug mode forf completion code"); $options['php-options'] = array('description' => "Options to pass to php when running drush. Only effective when using the `drush` script.", 'never-propagate' => TRUE); $options['deferred-sanitization'] = array('hidden' => TRUE, 'description' => "Defer calculating the sanitization operations until after the database has been copied. This is done automatically if the source database is remote."); $options['remote-host'] = array('hidden' => TRUE, 'description' => 'Remote site to execute drush command on. Managed by site alias.'); $options['remote-user'] = array('hidden' => TRUE, 'description' => 'User account to use with a remote drush command. Managed by site alias.'); $options['remote-os'] = array('hidden' => TRUE, 'description' => 'The operating system used on the remote host. Managed by site alias.'); $options['site-list'] = array('hidden' => TRUE, 'description' => 'List of sites to run commands on. Managed by site alias.'); $options['reserve-margin'] = array('hidden' => TRUE, 'description' => 'Remove columns from formatted opions. Managed by multi-site command handling.'); $options['strict'] = array('hidden' => TRUE, 'description' => 'Check requirements more strictly / remove backwards-compatibility features for unit tests.'); $options['command-specific'] = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'Command-specific options.'); $options['site-aliases'] = array('hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'List of site aliases.'); $options['shell-aliases'] = array('hidden' => TRUE, 'merge' => TRUE, 'never-propagate' => TRUE, 'description' => 'List of shell aliases.'); $options['path-aliases'] = array('hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Path aliases from site alias.'); $options['ssh-options'] = array('never-propagate' => TRUE, 'description' => 'A string of extra options that will be passed to the ssh command (e.g. "-p 100")'); $command = array( 'options' => $options, '#brief' => FALSE, ) + drush_command_defaults('global-options', 'global_options', __FILE__); drush_command_invoke_all_ref('drush_help_alter', $command); $options = $command['options']; } return $options; } /** * Exits with a message. In general, you should use drush_set_error() instead of * this function. That lets drush proceed with other tasks. * TODO: Exit with a correct status code. */ function drush_die($msg = NULL, $status = NULL) { die($msg ? "drush: $msg\n" : ''); } /* * Check to see if the provided line is a "#!/usr/bin/env drush" * "shebang" script line. */ function _drush_is_drush_shebang_line($line) { return ((substr($line,0,2) == '#!') && (strstr($line, 'drush') !== FALSE)); } /* * Check to see if the provided script file is a "#!/usr/bin/env drush" * "shebang" script line. */ function _drush_is_drush_shebang_script($script_filename) { $result = FALSE; if (file_exists($script_filename)) { $fp = fopen($script_filename, "r"); if ($fp !== FALSE) { $line = fgets($fp); $result = _drush_is_drush_shebang_line($line); fclose($fp); } } return $result; } /** * @defgroup userinput Get input from the user. * @{ */ /** * Asks the user a basic yes/no question. * * @param string $msg * The question to ask. * @param int $indent * The number of spaces to indent the message. * * @return bool * TRUE if the user enters "y" or FALSE if "n". */ function drush_confirm($msg, $indent = 0) { drush_print_prompt((string)$msg . " (y/n): ", $indent); // Automatically accept confirmations if the --yes argument was supplied. if (drush_get_context('DRUSH_AFFIRMATIVE')) { drush_print("y"); return TRUE; } // Automatically cancel confirmations if the --no argument was supplied. elseif (drush_get_context('DRUSH_NEGATIVE')) { drush_print("n"); return FALSE; } // See http://drupal.org/node/499758 before changing this. $stdin = fopen("php://stdin","r"); while ($line = fgets($stdin)) { $line = trim($line); if ($line == 'y') { return TRUE; } if ($line == 'n') { return FALSE; } drush_print_prompt((string)$msg . " (y/n): ", $indent); } } /** * Ask the user to select an item from a list. * From a provided associative array, drush_choice will * display all of the questions, numbered from 1 to N, * and return the item the user selected. "0" is always * cancel; entering a blank line is also interpreted * as cancelling. * * @param $options * A list of questions to display to the user. The * KEYS of the array are the result codes to return to the * caller; the VALUES are the messages to display on * each line. Special keys of the form '-- something --' can be * provided as separator between choices groups. Separator keys * don't alter the numbering. * @param $prompt * The message to display to the user prompting for input. * @param $label * Controls the display of each line. Defaults to * '!value', which displays the value of each item * in the $options array to the user. Use '!key' to * display the key instead. In some instances, it may * be useful to display both the key and the value; for * example, if the key is a user id and the value is the * user name, use '!value (uid=!key)'. */ function drush_choice($options, $prompt = 'Enter a number.', $label = '!value', $widths = array()) { drush_print(dt($prompt)); // Preflight so that all rows will be padded out to the same number of columns $array_pad = 0; foreach ($options as $key => $option) { if (is_array($option) && (count($option) > $array_pad)) { $array_pad = count($option); } } $rows[] = array_pad(array('[0]', ':', 'Cancel'), $array_pad + 2, ''); $selection_number = 0; foreach ($options as $key => $option) { if ((substr($key, 0, 3) == '-- ') && (substr($key, -3) == ' --')) { $rows[] = array_pad(array('', '', $option), $array_pad + 2, ''); } else { $selection_number++; $row = array("[$selection_number]", ':'); if (is_array($option)) { $row = array_merge($row, $option); } else { $row[] = dt($label, array('!number' => $selection_number, '!key' => $key, '!value' => $option)); } $rows[] = $row; $selection_list[$selection_number] = $key; } } drush_print_table($rows, FALSE, $widths); drush_print_pipe(array_keys($options)); // If the user specified --choice, then make an // automatic selection. Cancel if the choice is // not an available option. if (($choice = drush_get_option('choice', FALSE)) !== FALSE) { // First check to see if $choice is one of the symbolic options if (array_key_exists($choice, $options)) { return $choice; } // Next handle numeric selections elseif (array_key_exists($choice, $selection_list)) { return $selection_list[$choice]; } return FALSE; } // If the user specified --no, then cancel; also avoid // getting hung up waiting for user input in --pipe and // backend modes. If none of these apply, then wait, // for user input and return the selected result. if (!drush_get_context('DRUSH_NEGATIVE') && !drush_get_context('DRUSH_AFFIRMATIVE') && !drush_get_context('DRUSH_PIPE')) { while ($line = trim(fgets(STDIN))) { if (array_key_exists($line, $selection_list)) { return $selection_list[$line]; } } } // We will allow --yes to confirm input if there is only // one choice; otherwise, --yes will cancel to avoid ambiguity if (drush_get_context('DRUSH_AFFIRMATIVE') && (count($options) == 1)) { return $selection_list[1]; } drush_print(dt('Cancelled')); return FALSE; } /** * Ask the user to select multiple items from a list. * This is a wrapper around drush_choice, that repeats the selection process, * allowing users to toggle a number of items in a list. The number of values * that can be constrained by both min and max: the user will only be allowed * finalize selection once the minimum number has been selected, and the oldest * selected value will "drop off" the list, if they exceed the maximum number. * * @param $options * Same as drush_choice() (see above). * @param $defaults * This can take 3 forms: * - FALSE: (Default) All options are unselected by default. * - TRUE: All options are selected by default. * - Array of $options keys to be selected by default. * @param $prompt * Same as drush_choice() (see above). * @param $label * Same as drush_choice() (see above). * @param $mark * Controls how selected values are marked. Defaults to '!value (selected)'. * @param $min * Constraint on minimum number of selections. Defaults to zero. When fewer * options than this are selected, no final options will be available. * @param $max * Constraint on minimum number of selections. Defaults to NULL (unlimited). * If the a new selection causes this value to be exceeded, the oldest * previously selected value is automatically unselected. * @param $final_options * An array of additional options in the same format as $options. * When the minimum number of selections is met, this array is merged into the * array of options. If the user selects one of these values and the * selection process will complete (the key for the final option is included * in the return value). If this is an empty array (default), then a built in * final option of "Done" will be added to the available options (in this case * no additional keys are added to the return value). */ function drush_choice_multiple($options, $defaults = FALSE, $prompt = 'Select some numbers.', $label = '!value', $mark = '!value (selected)', $min = 0, $max = NULL, $final_options = array()) { $selections = array(); // Load default selections. if (is_array($defaults)) { $selections = $defaults; } elseif ($defaults === TRUE) { $selections = array_keys($options); } $complete = FALSE; $final_builtin = array(); if (empty($final_options)) { $final_builtin['done'] = dt('Done'); } $final_options_keys = array_keys($final_options); while (TRUE) { $current_options = $options; // Mark selections. foreach ($selections as $selection) { $current_options[$selection] = dt($mark, array('!key' => $selection, '!value' => $options[$selection])); } // Add final options, if the minimum number of selections has been reached. if (count($selections) >= $min) { $current_options = array_merge($current_options, $final_options, $final_builtin); } $toggle = drush_choice($current_options, $prompt, $label); if ($toggle === FALSE) { return FALSE; } // Don't include the built in final option in the return value. if (count($selections) >= $min && empty($final_options) && $toggle == 'done') { return $selections; } // Toggle the selected value. $item = array_search($toggle, $selections); if ($item === FALSE) { array_unshift($selections, $toggle); } else { unset($selections[$item]); } // If the user selected one of the final options, return. if (count($selections) >= $min && in_array($toggle, $final_options_keys)) { return $selections; } // If the user selected too many options, drop the oldest selection. if (isset($max) && count($selections) > $max) { array_pop($selections); } } } /** * Prompt the user for input * * The input can be anything that fits on a single line (not only y/n), * so we can't use drush_confirm() * * @param $prompt * The text which is displayed to the user. * @param $default * The default value of the input. * @param $required * If TRUE, user may continue even when no value is in the input. * @param $password * If TRUE, surpress printing of the input. * * @see drush_confirm() */ function drush_prompt($prompt, $default = NULL, $required = TRUE, $password = FALSE) { if (!is_null($default)) { $prompt .= " [" . $default . "]"; } $prompt .= ": "; drush_print_prompt($prompt); if (drush_get_context('DRUSH_AFFIRMATIVE')) { return $default; } $stdin = fopen('php://stdin', 'r'); if ($password) drush_shell_exec("stty -echo"); stream_set_blocking($stdin, TRUE); while (($line = fgets($stdin)) !== FALSE) { $line = trim($line); if ($line === "") { $line = $default; } if ($line || !$required) { break; } drush_print_prompt($prompt); } fclose($stdin); if ($password) { drush_shell_exec("stty echo"); print "\n"; } return $line; } /** * @} End of "defgroup userinput". */ /** * Calls a given function, passing through all arguments unchanged. * * This should be used when calling possibly mutative or destructive functions * (e.g. unlink() and other file system functions) so that can be suppressed * if the simulation mode is enabled. * * Important: Call @see drush_op_system() to execute a shell command, * or @see drush_shell_exec() to execute a shell command and capture the * shell output. * * @param $function * The name of the function. Any additional arguments are passed along. * @return * The return value of the function, or TRUE if simulation mode is enabled. * */ function drush_op($function) { $args_printed = array(); $args = func_get_args(); array_shift($args); // Skip function name foreach ($args as $arg) { $args_printed[] = is_scalar($arg) ? $arg : (is_array($arg) ? 'Array' : 'Object'); } // Special checking for drush_op('system') if ($function == 'system') { drush_log(dt("Do not call drush_op('system'); use drush_op_system instead"), 'debug'); } if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { drush_log(sprintf("Calling %s(%s)", $function, implode(", ", $args_printed)), 'debug'); } if (drush_get_context('DRUSH_SIMULATE')) { return TRUE; } return call_user_func_array($function, $args); } /** * Download a file using wget, curl or file_get_contents, or via download cache. * * @param string $url * The url of the file to download. * @param string $destination * The name of the file to be saved, which may include the full path. * Optional, if omitted the filename will be extracted from the url and the * file downloaded to the current working directory (Drupal root if * bootstrapped). * @param integer $cache_duration * The acceptable age of a cached file. If cached file is too old, a fetch * will occur and cache will be updated. Optional, if ommitted the file will * be fetched directly. * * @return string * The path to the downloaded file, or FALSE if the file could not be * downloaded. */ function drush_download_file($url, $destination = FALSE, $cache_duration = 0) { // Generate destination of omitted. if (!$destination) { $file = basename(current(explode('?', $url, 2))); $destination = getcwd() . '/' . basename($file); } if (drush_get_option('cache') && $cache_duration !== 0 && $cache_dir = drush_directory_cache('download')) { $cache_name = str_replace(array(':', '/', '?', '='), '-', $url); $cache_file = $cache_dir . "/" . $cache_name; // Check for cached, unexpired file. if (file_exists($cache_file) && filectime($cache_file) > ($_SERVER['REQUEST_TIME']-$cache_duration)) { drush_log(dt('!name retrieved from cache.', array('!name' => $cache_name))); } else { if (_drush_download_file($url, $cache_file, TRUE)) { // Cache was set just by downloading file to right location. } elseif (file_exists($cache_file)) { drush_log(dt('!name retrieved from an expired cache since refresh failed.', array('!name' => $cache_name)), 'warning'); } else { $cache_file = FALSE; } } if ($cache_file && copy($cache_file, $destination)) { // Copy cached file to the destination return $destination; } } elseif ($return = _drush_download_file($url, $destination)) { drush_register_file_for_deletion($return); return $return; } // Unable to retrieve from cache nor download. return FALSE; } /** * Download a file using wget, curl or file_get_contents. Does not use download * cache. * * @param string $url * The url of the file to download. * @param string $destination * The name of the file to be saved, which may include the full path. * @param boolean $overwrite * Overwrite any file thats already at the destination. * @return string * The path to the downloaded file, or FALSE if the file could not be * downloaded. */ function _drush_download_file($url, $destination, $overwrite = TRUE) { static $use_wget; if ($use_wget === NULL) { $use_wget = drush_shell_exec('wget --version'); } $destination_tmp = drush_tempnam('download_file'); if ($use_wget) { drush_shell_exec("wget -q --timeout=30 -O %s %s", $destination_tmp, $url); } else { drush_shell_exec("curl --fail -s -L --connect-timeout 30 -o %s %s", $destination_tmp, $url); } if (!drush_file_not_empty($destination_tmp) && $file = @file_get_contents($url)) { @file_put_contents($destination_tmp, $file); } if (!drush_file_not_empty($destination_tmp)) { // Download failed. return FALSE; } drush_move_dir($destination_tmp, $destination, $overwrite); return $destination; } /** * Determines the MIME content type of the specified file. * * The power of this function depends on whether the PHP installation * has either mime_content_type() or finfo installed -- if not, only tar, * gz, zip and bzip2 types can be detected. * * If mime type can't be obtained, an error will be set. * * @return mixed * The MIME content type of the file or FALSE. */ function drush_mime_content_type($filename) { // 1. Try to use php built-ins. $content_type = FALSE; if (class_exists('finfo')) { drush_log(dt('Fileinfo extension available.'), 'debug'); // For pecl's fileinfo on php 5.2 there is quite some inconsistency in // distributions and loading of magic files. // TODO: remove @ and use FILEINFO_MIME_TYPE when drush requires php 5.3 $finfo = @new finfo(FILEINFO_MIME); $ct = @$finfo->file($filename); if ($ct) { // We only want the first part, before the ; $content_type = current(explode(';', $ct)); } } // TODO: to be removed in php 5.3 (deprecated). if ((!$content_type) && (function_exists('mime_content_type'))) { drush_log(dt('mime_magic support enabled.'), 'debug'); $content_type = trim(mime_content_type($filename)); } if (!$content_type) { drush_log(dt('No fileinfo or mime_magic support available.'), 'debug'); } elseif ($content_type == 'application/octet-stream') { drush_log(dt('Mime type for !file is application/octet-stream.', array('!file' => $filename)), 'debug'); $content_type = FALSE; } // 2. if PHP's built-ins aren't present or PHP is configured in such a way // that all these files are considered octet-stream (e.g with mod_mime_magic // and an http conf that's serving all archives as octet-stream for other // reasons) we'll detect (a few select) mime types on our own by examing the // file's magic header bytes. if (!$content_type) { drush_log(dt('Examining !file headers.', array('!file' => $filename)), 'debug'); if ($file = fopen($filename, 'rb')) { $first = fread($file, 2); fclose($file); if ($first !== FALSE) { // Interpret the two bytes as a little endian 16-bit unsigned int. $data = unpack('v', $first); switch ($data[1]) { case 0x8b1f: // First two bytes of gzip files are 0x1f, 0x8b (little-endian). // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer $content_type = 'application/x-gzip'; break; case 0x4b50: // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian). // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers $content_type = 'application/zip'; break; case 0x5a42: // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian). // See http://en.wikipedia.org/wiki/Bzip2#File_format $content_type = 'application/x-bzip2'; break; default: drush_log(dt('Unable to determine mime type from header bytes 0x!hex of !file.', array('!hex' => dechex($data[1]), '!file' => $filename,), 'debug')); } } else { drush_log(dt('Unable to read !file.', array('!file' => $filename)), 'warning'); } } else { drush_log(dt('Unable to open !file.', array('!file' => $filename)), 'warning'); } } // 3. Lastly if above methods didn't work, try to guess the mime type from // the file extension. This is useful if the file has no identificable magic // header bytes (for example tarballs). if (!$content_type) { drush_log(dt('Examining !file extension.', array('!file' => $filename)), 'debug'); // Remove querystring from the filename, if present. $filename = basename(current(explode('?', $filename, 2))); $extension_mimetype = array( '.tar' => 'application/x-tar', ); foreach ($extension_mimetype as $extension => $ct) { if (substr($filename, -strlen($extension)) === $extension) { $content_type = $ct; break; } } } if ($content_type) { drush_log(dt('Mime type for !file is !mt', array('!file' => $filename, '!mt' => $content_type)), 'notice'); return $content_type; } return drush_set_error('MIME_CONTENT_TYPE_UNKNOWN', dt('Unable to determine mime type for !file.', array('!file' => $filename))); } /** * Check whether a file is a supported tarball. * * @return mixed * The file content type if it's a tarball. FALSE otherwise. */ function drush_file_is_tarball($path) { $content_type = drush_mime_content_type($path); $supported = array( 'application/x-bzip2', 'application/x-gzip', 'application/x-tar', 'application/x-zip', 'application/zip', ); if (in_array($content_type, $supported)) { return $content_type; } return FALSE; } /** * Extract a tarball. * * @param string $path * Path to the archive to be extracted. * @param string $destination * The destination directory the tarball should be extracted into. * Optional, if ommitted the tarball directory will be used as destination. * @param boolean $listing * If TRUE, a listing of the tar contents will be returned on success. * * @return mixed * TRUE on success, FALSE on fail. If $listing is TRUE, a file listing of the * tarball is returned if the extraction reported success, instead of TRUE. */ function drush_tarball_extract($path, $destination = FALSE, $listing = FALSE) { // Check if tarball is supported. if (!($mimetype = drush_file_is_tarball($path))) { return drush_set_error('TARBALL_EXTRACT_UNKNOWN_FORMAT', dt('Unable to extract !path. Unknown archive format.', array('!path' => $path))); } // Check if destination is valid. if (!$destination) { $destination = dirname($path); } if (!drush_mkdir($destination)) { // drush_mkdir already set an error. return FALSE; } // Perform the extraction of a zip file. if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) { $return = drush_shell_cd_and_exec(dirname($path), "unzip %s -d %s", $path, $destination); if (!$return) { return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to unzip !filename.', array('!filename' => $path))); } if ($listing) { // unzip prefixes the file listing output with a header line, // and prefixes each line with a verb representing the compression type. $output = drush_shell_exec_output(); // Remove the header line. array_shift($output); // Remove the prefix verb from each line. $output = array_map(create_function('$str', 'return substr($str, strpos($str, ":") + 3 + ' . strlen($destination) . ');'), $output); // Remove any remaining blank lines. $return = array_filter($output, create_function('$str', 'return $str != "";')); } } // Otherwise we have a possibly-compressed Tar file. // If we are not on Windows, then try to do "tar" in a single operation. elseif (!drush_is_windows()) { $tar = drush_get_tar_executable(); $tar_compression_flag = ''; if ($mimetype == 'application/x-gzip') { $tar_compression_flag = 'z'; } elseif ($mimetype == 'application/x-bzip2') { $tar_compression_flag = 'j'; } $return = drush_shell_cd_and_exec(dirname($path), "$tar -C %s -x%sf %s", $destination, $tar_compression_flag, basename($path)); if (!$return) { return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $path))); } if ($listing) { // We use a separate tar -tf instead of -xvf above because // the output is not the same in Mac. drush_shell_cd_and_exec(dirname($path), "$tar -t%sf %s", $tar_compression_flag, basename($path)); $return = drush_shell_exec_output(); } } // In windows, do the extraction by its primitive steps. else { // 1. copy the source tarball to the destination directory. Rename to a // temp name in case the destination directory == dirname($path) $tmpfile = drush_tempnam(basename($path), $destination); drush_copy_dir($path, $tmpfile, FILE_EXISTS_OVERWRITE); // 2. uncompress the tarball, if compressed. if (($mimetype == 'application/x-gzip') || ($mimetype == 'application/x-bzip2')) { if ($mimetype == 'application/x-gzip') { $compressed = $tmpfile . '.gz'; // We used to use gzip --decompress in --stdout > out, but the output // redirection sometimes failed on Windows for some binary output. $command = 'gzip --decompress %s'; } elseif ($mimetype == 'application/x-bzip2') { $compressed = $tmpfile . '.bz2'; $command = 'bzip2 --decompress %s'; } drush_op('rename', $tmpfile, $compressed); $return = drush_shell_cd_and_exec(dirname($compressed), $command, $compressed); if (!$return || !file_exists($tmpfile)) { return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to decompress !filename.', array('!filename' => $compressed))); } } // 3. Untar. $tar = drush_get_tar_executable(); $return = drush_shell_cd_and_exec(dirname($tmpfile), "$tar -xvf %s", basename($tmpfile)); if (!$return) { return drush_set_error('DRUSH_TARBALL_EXTRACT_ERROR', dt('Unable to untar !filename.', array('!filename' => $tmpfile))); } if ($listing) { $return = drush_shell_exec_output(); // Cut off the 'x ' prefix for the each line of the tar output // See http://drupal.org/node/1775520 foreach($return as &$line) { if(strpos($line, "x ") === 0) $line = substr($line, 2); } } // Remove the temporary file so the md5 hash is accurate. unlink($tmpfile); } return $return; } /** * Download and extract a tarball to the lib directory. * * Checks for reported success, but callers should normally check for existence * of specific expected file(s) in the library. * * @param string $url * The URL to the library tarball. * * @return boolean * TRUE is the download and extraction reported success, FALSE otherwise. */ function drush_lib_fetch($url) { $lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib'); if (!is_writable($lib)) { return drush_set_error('DRUSH_LIB_UNWRITABLE', dt("Drush needs to download a library from !url in order to function, and the attempt to download this file automatically failed because you do not have permission to write to the library directory !path. To continue you will need to manually download the package from !url, extract it, and copy the directory into your !path directory.", array('!path' => $lib, '!url' => $url))); } $destination = $lib . '/drush-library-' . mt_rand(); $path = drush_download_file($url, $destination); if (!$path) { return FALSE; } return drush_tarball_extract($path); } /** * @defgroup commandprocessing Command processing functions. * @{ * * These functions manage command processing by the * main function in drush.php. */ /** * Handle any command preprocessing that may need to be done, including * potentially redispatching the command immediately (e.g. for remote * commands). * * @return * TRUE if the command was handled remotely. */ function drush_preflight_command_dispatch() { $interactive = drush_get_option('interactive', FALSE); // The command will be executed remotely if the --remote-host flag // is set; note that if a site alias is provided on the command line, // and the site alias references a remote server, then the --remote-host // option will be set when the site alias is processed. // @see drush_sitealias_check_arg $remote_host = drush_get_option('remote-host'); // Get the command early so that we can allow commands to directly handle remote aliases if they wish $command = drush_parse_command(); if (isset($command)) { drush_command_default_options($command); } // If the command sets the 'strict-option-handling' flag, then we will remove // any cli options that appear after the command name form the 'cli' context. // The cli options that appear before the command name are stored in the // 'DRUSH_GLOBAL_CLI_OPTIONS' context, so we will just overwrite the cli context // with this, after doing the neccessary fixup from short-form to long-form options. // After we do that, we put back any local drush options identified by $command['options']. if (is_array($command) && !empty($command['strict-option-handling'])) { $cli_options = drush_get_context('DRUSH_GLOBAL_CLI_OPTIONS', array()); // Now we are going to sort out any options that exist in $command['options']; // we will remove these from DRUSH_COMMAND_ARGS and put them back into the // cli options. $cli_context = drush_get_context('cli'); $remove_from_command_args = array(); foreach ($command['options'] as $option => $info) { if (array_key_exists($option, $cli_context)) { $cli_options[$option] = $cli_context[$option]; $remove_from_command_args[$option] = $option; } } if (!empty($remove_from_command_args)) { $drush_command_args = array(); foreach (drush_get_context('DRUSH_COMMAND_ARGS') as $arg) { if (!_drush_should_remove_command_arg($arg, $remove_from_command_args)) { $drush_command_args[] = $arg; } } drush_set_context('DRUSH_COMMAND_ARGS', $drush_command_args); } drush_expand_short_form_options($cli_options); drush_set_context('cli', $cli_options); _drush_bootstrap_global_options(); } // If the command sets the 'handle-remote-commands' flag, then we will short-circuit // remote command dispatching and site-list command dispatching, and always let // the command handler run on the local machine. if (is_array($command) && !empty($command['handle-remote-commands'])) { return FALSE; } if (isset($remote_host)) { $args = drush_get_arguments(); $command_name = array_shift($args); $remote_user = drush_get_option('remote-user'); // Force interactive mode if there is a single remote target. #interactive is added by drush_do_command_redispatch drush_set_option('interactive', TRUE); $values = drush_do_command_redispatch(isset($command) ? $command : $command_name, $args, $remote_host, $remote_user); // In 'interactive' mode, $values is the result code from drush_shell_proc_open. // TODO: in _drush_backend_invoke, return array('error_status' => $ret) instead for uniformity. if (!is_array($values) && ($values != 0)) { // Force an error result code. Note that drush_shutdown() will still run. drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE); exit($values); } return TRUE; } // If the --site-list flag is set, then we will execute the specified // command once for every site listed in the site list. $site_list = drush_get_option('site-list'); if (isset($site_list)) { if (!is_array($site_list)) { $site_list = explode(',', $site_list); } $site_record = array('site-list' => $site_list); $args = drush_get_arguments(); if (!drush_get_context('DRUSH_SIMULATE') && !$interactive) { drush_print(dt("You are about to execute '!command' non-interactively (--yes forced) on all of the following targets:", array('!command' => implode(" ", $args)))); foreach ($site_list as $one_destination) { drush_print(dt(' !target', array('!target' => $one_destination))); } if (drush_confirm('Continue? ') === FALSE) { drush_user_abort(); return TRUE; } } $command_name = array_shift($args); $multi_options = drush_get_context('cli'); $backend_options = array(); if (drush_get_option('pipe') || drush_get_option('interactive')) { $backend_options['interactive'] = TRUE; } if (drush_get_option('no-label', FALSE)) { $backend_options['no-label'] = TRUE; } $values = drush_invoke_process($site_record, $command_name, $args, $multi_options, $backend_options); return TRUE; } return FALSE; } /** * Determine whether or not an argument should be removed from the * DRUSH_COMMAND_ARGS context. This method is used when a Drush * command has set the 'strict-option-handling' flag indicating * that it will pass through all commandline arguments and any * additional options (not known to Drush) to some shell command. * * Take as an example the following call to core-rsync: * * drush --yes core-rsync -v -az --exclude-paths='.git:.svn' local-files/ @site:%files * * In this instance: * * --yes is a global Drush option * * -v is an rsync option. It will make rsync run in verbose mode, * but will not make Drush run in verbose mode due to the fact that * core-rsync sets the 'strict-option-handling' flag. * * --exclude-paths is a local Drush option. It will be converted by * Drush into --exclude='.git' and --exclude='.svn', and then passed * on to the rsync command. * * The parameter $arg passed to this function is one of the elements * of DRUSH_COMMAND_ARGS. It will have values such as: * -v * -az * --exclude-paths='.git:.svn' * local-files/ * @site:%files * * Our job in this function is to determine if $arg should be removed * by virtue of appearing in $removal_list. $removal_list is an array * that will contain values such as 'exclude-paths'. Both the key and * the value of $removal_list is the same. */ function _drush_should_remove_command_arg($arg, $removal_list) { foreach ($removal_list as $candidate) { if (($arg == "-$candidate") || ($arg == "--$candidate") || (substr($arg,0,strlen($candidate)+3) == "--$candidate=") ) { return TRUE; } } return FALSE; } /** * Used by functions that operate on lists of sites, moving * information from the source to the destination. Currenlty * this includes 'drush rsync' and 'drush sql sync'. */ function drush_do_multiple_command($command, $source_record, $destination_record, $allow_single_source = FALSE) { $is_multiple_command = FALSE; if ((($allow_single_source == TRUE) || array_key_exists('site-list', $source_record)) && array_key_exists('site-list', $destination_record)) { $is_multiple_command = TRUE; $source_path = array_key_exists('path-component', $source_record) ? $source_record['path-component'] : ''; $destination_path = array_key_exists('path-component', $destination_record) ? $destination_record['path-component'] : ''; $target_list = array_values(drush_sitealias_resolve_sitelist($destination_record)); if (array_key_exists('site-list', $source_record)) { $source_list = array_values(drush_sitealias_resolve_sitelist($source_record)); if (drush_sitealias_check_lists_alignment($source_list, $target_list) === FALSE) { if (array_key_exists('unordered-list', $source_record) || array_key_exists('unordered-list', $destination_record)) { drush_sitelist_align_lists($source_list, $target_list, $aligned_source, $aligned_target); $source_list = $aligned_source; $target_list = $aligned_target; } } } else { $source_list = array_fill(0, count($target_list), $source_record); } if (!drush_get_context('DRUSH_SIMULATE')) { drush_print(dt('You are about to !command between all of the following targets:', array('!command' => $command))); $i = 0; foreach ($source_list as $one_source) { $one_target = $target_list[$i]; ++$i; drush_print(dt(' !source will overwrite !target', array('!source' => drush_sitealias_alias_record_to_spec($one_source) . $source_path, '!target' => drush_sitealias_alias_record_to_spec($one_target) . $destination_path))); } if (drush_confirm('Continue? ') === FALSE) { return drush_user_abort(); } } $data = drush_redispatch_get_options(); $i = 0; foreach ($source_list as $one_source) { $one_target = $target_list[$i]; ++$i; $source_spec = drush_sitealias_alias_record_to_spec($one_source); $target_spec = drush_sitealias_alias_record_to_spec($one_target); drush_log(dt('Begin do_multiple !command via backend invoke', array('!command' => $command))); $values = drush_invoke_process('@self', $command, array($source_spec . $source_path, $target_spec . $destination_path), $data); drush_log(dt('Backend invoke is complete')); } } return $is_multiple_command; } /** * Run a command on the site specified by the provided command record. * * The standard function that provides this service is called * drush_invoke_process. Please call the standard function. * * @param backend_options * TRUE - integrate errors * FALSE - do not integrate errors * array - @see drush_backend_invoke_concurrent * function drush_do_site_command($site_record, $command, $args = array(), $command_options = array(), $backend_options = FALSE) { return drush_invoke_process($site_record, $command, $args, $command_options, $backend_options); } */ /** * Redispatch the specified command using the same * options that were passed to this invocation of drush. */ function drush_do_command_redispatch($command, $args = array(), $remote_host = NULL, $remote_user = NULL, $drush_path = NULL) { $additional_global_options = array(); $command_options = drush_redispatch_get_options(); if (is_array($command)) { $command_name = $command['command']; // If we are executing a remote command that uses strict option handling, // then mark all of the options in the alias context as global, so that they // will appear before the command name. if (!empty($command['strict-option-handling'])) { foreach(drush_get_context('alias') as $alias_key => $alias_value) { if (array_key_exists($alias_key, $command_options) && !array_key_exists($alias_key, $command['options'])) { $additional_global_options[$alias_key] = $alias_value; } } } } else { $command_name = $command; } // If the path to drush was supplied, then use it to invoke the new command. if ($drush_path == NULL) { $drush_path = drush_get_option('drush-script'); if (!isset($drush_path)) { $drush_folder = drush_get_option('drush'); if (isset($drush)) { $drush_path = $drush_folder . '/drush'; } } } $backend_options = array('drush-script' => $drush_path, 'remote-host' => $remote_host, 'remote-user' => $remote_user, 'integrate' => TRUE, 'additional-global-options' => $additional_global_options); if (drush_get_option('interactive')) { $backend_options['interactive'] = TRUE; } // Run the command in a new process. drush_log(dt('Begin redispatch via invoke process')); $values = drush_invoke_process('@self', $command_name, $args, $command_options, $backend_options); drush_log(dt('Invoke process is complete')); return $values; } /** * @} End of "defgroup commandprocessing". */ /** * @defgroup logging Logging information to be provided as output. * @{ * * These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken * by drush. */ /** * Add a log message to the log history. * * This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with * the resulting entry at the end of execution. * * This allows you to replace it with custom logging implementations if needed, * such as logging to a file or logging to a database (drupal or otherwise). * * The default callback is the _drush_print_log() function with prints the messages * to the shell. * * @param message * String containing the message to be logged. * @param type * The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'. * A type of 'failed' can also be supplied to flag as an 'error'. * A type of 'ok' or 'completed' can also be supplied to flag as a 'success' * All other types of messages will be assumed to be notices. */ function drush_log($message, $type = 'notice', $error = null) { $log =& drush_get_context('DRUSH_LOG', array()); $callback = drush_get_context('DRUSH_LOG_CALLBACK', '_drush_print_log'); $entry = array( 'type' => $type, 'message' => $message, 'timestamp' => microtime(TRUE), 'memory' => memory_get_usage(), ); $entry['error'] = $error; $log[] = $entry; drush_backend_packet('log', $entry); return $callback($entry); } /** * Backend command callback. Add a log message to the log history. * * @param entry * The log entry. */ function drush_backend_packet_log($entry, $backend_options) { if (!$backend_options['log']) { return; } if (!is_string($entry['message'])) { $entry['message'] = implode("\n", (array)$entry['message']); } $entry['message'] = $entry['message']; $log =& drush_get_context('DRUSH_LOG', array()); $log[] = $entry; // Yes, this looks odd, but we might in fact be a backend command // that ran another backend command. drush_backend_packet('log', $entry); if (array_key_exists('#output-label', $backend_options)) { $entry['message'] = $backend_options['#output-label'] . $entry['message']; } // If integrate is FALSE, then log messages are stored in DRUSH_LOG, // but are -not- printed to the console. if ($backend_options['integrate']) { $callback = drush_get_context('DRUSH_LOG_CALLBACK', '_drush_print_log'); return $callback($entry); } } /** * Retrieve the log messages from the log history * * @return * Entire log history */ function drush_get_log() { return drush_get_context('DRUSH_LOG', array()); } /** * Run print_r on a variable and log the output. */ function dlm($object) { drush_log(print_r($object, TRUE)); } /** * Display the pipe output for the current request. */ function drush_pipe_output() { $pipe = drush_get_context('DRUSH_PIPE_BUFFER'); if (!empty($pipe)) { drush_print_r($pipe, NULL, FALSE); } } /** * Display the log message * * By default, only warnings and errors will be displayed, if 'verbose' is specified, it will also display notices. * * @param * The associative array for the entry. * * @return * False in case of an error or failed type, True in all other cases. */ function _drush_print_log($entry) { if (drush_get_context('DRUSH_NOCOLOR')) { $red = "[%s]"; $yellow = "[%s]"; $green = "[%s]"; } else { $red = "\033[31;40m\033[1m[%s]\033[0m"; $yellow = "\033[1;33;40m\033[1m[%s]\033[0m"; $green = "\033[1;32;40m\033[1m[%s]\033[0m"; } $verbose = drush_get_context('DRUSH_VERBOSE'); $debug = drush_get_context('DRUSH_DEBUG'); $return = TRUE; switch ($entry['type']) { case 'warning' : case 'cancel' : $type_msg = sprintf($yellow, $entry['type']); break; case 'failed' : case 'error' : $type_msg = sprintf($red, $entry['type']); $return = FALSE; break; case 'ok' : case 'completed' : case 'success' : case 'status': // In quiet mode, suppress progress messages if (drush_get_context('DRUSH_QUIET')) { return TRUE; } $type_msg = sprintf($green, $entry['type']); break; case 'notice' : case 'message' : case 'info' : if (!$verbose) { // print nothing. exit cleanly. return TRUE; } $type_msg = sprintf("[%s]", $entry['type']); break; default : if (!$debug) { // print nothing. exit cleanly. return TRUE; } $type_msg = sprintf("[%s]", $entry['type']); break; } // When running in backend mode, log messages are not displayed, as they will // be returned in the JSON encoded associative array. if (drush_get_context('DRUSH_BACKEND')) { return $return; } $columns = drush_get_context('DRUSH_COLUMNS', 80); $width[1] = 11; // Append timer and memory values. if ($debug) { $timer = sprintf('[%s sec, %s]', round($entry['timestamp']-DRUSH_REQUEST_TIME, 2), drush_format_size($entry['memory'])); $entry['message'] = $entry['message'] . ' ' . $timer; } $width[0] = ($columns - 11); $format = sprintf("%%-%ds%%%ds", $width[0], $width[1]); // Place the status message right aligned with the top line of the error message. $message = wordwrap($entry['message'], $width[0]); $lines = explode("\n", $message); $lines[0] = sprintf($format, $lines[0], $type_msg); $message = implode("\n", $lines); drush_print($message, 0, STDERR); return $return; } // Print all timers for the request. function drush_print_timers() { global $timers; $temparray = array(); foreach ((array)$timers as $name => $timerec) { // We have to use timer_read() for active timers, and check the record for others if (isset($timerec['start'])) { $temparray[$name] = timer_read($name); } else { $temparray[$name] = $timerec['time']; } } // Go no farther if there were no timers if (count($temparray) > 0) { // Put the highest cumulative times first arsort($temparray); $table = array(); $table[] = array('Timer', 'Cum (sec)', 'Count', 'Avg (msec)'); foreach ($temparray as $name => $time) { $cum = round($time/1000, 3); $count = $timers[$name]['count']; if ($count > 0) { $avg = round($time/$count, 3); } else { $avg = 'N/A'; } $table[] = array($name, $cum, $count, $avg); } drush_print_table($table, TRUE, array(), STDERR); } } /** * Turn drupal_set_message errors into drush_log errors */ function _drush_log_drupal_messages() { if (function_exists('drupal_get_messages')) { $messages = drupal_get_messages(NULL, TRUE); if (array_key_exists('error', $messages)) { //Drupal message errors. foreach ((array) $messages['error'] as $error) { $error = strip_tags($error); $header = preg_match('/^warning: Cannot modify header information - headers already sent by /i', $error); $session = preg_match('/^warning: session_start\(\): Cannot send session /i', $error); if ($header || $session) { //These are special cases for an unavoidable warnings //that are generated by generating output before Drupal is bootstrapped. //or sending a session cookie (seems to affect d7 only?) //Simply ignore them. continue; } elseif (preg_match('/^warning:/i', $error)) { drush_log(preg_replace('/^warning: /i', '', $error), 'warning'); } elseif (preg_match('/^notice:/i', $error)) { drush_log(preg_replace('/^notice: /i', '', $error), 'notice'); } elseif (preg_match('/^user warning:/i', $error)) { // This is a special case. PHP logs sql errors as 'User Warnings', not errors. drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', preg_replace('/^user warning: /i', '', $error)); } else { drush_set_error('DRUSH_DRUPAL_ERROR_MESSAGE', $error); } } } unset($messages['error']); // Log non-error messages. foreach ($messages as $type => $items) { foreach ($items as $item) { drush_log(strip_tags($item), $type); } } } } // Copy of format_size() in Drupal. function drush_format_size($size, $langcode = NULL) { if ($size < DRUSH_DRUPAL_KILOBYTE) { // format_plural() not always available. return dt('@count bytes', array('@count' => $size)); } else { $size = $size / DRUSH_DRUPAL_KILOBYTE; // Convert bytes to kilobytes. $units = array( dt('@size KB', array(), array('langcode' => $langcode)), dt('@size MB', array(), array('langcode' => $langcode)), dt('@size GB', array(), array('langcode' => $langcode)), dt('@size TB', array(), array('langcode' => $langcode)), dt('@size PB', array(), array('langcode' => $langcode)), dt('@size EB', array(), array('langcode' => $langcode)), dt('@size ZB', array(), array('langcode' => $langcode)), dt('@size YB', array(), array('langcode' => $langcode)), ); foreach ($units as $unit) { if (round($size, 2) >= DRUSH_DRUPAL_KILOBYTE) { $size = $size / DRUSH_DRUPAL_KILOBYTE; } else { break; } } return str_replace('@size', round($size, 2), $unit); } } /** * @} End of "defgroup logging". */ /** * @defgroup errorhandling Managing errors that occur in the Drush framework. * @{ * Functions that manage the current error status of the Drush framework. * * These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an * error has occurred. * This error code is returned at the end of program execution, and provide the shell or calling application with * more information on how to diagnose any problems that may have occurred. */ /** * Set an error code for the error handling system. * * @param error * A text string identifying the type of error. * * @param message * Optional. Error message to be logged. If no message is specified, hook_drush_help will be consulted, * using a key of 'error:MY_ERROR_STRING'. * * @return * Always returns FALSE, to allow you to return with false in the calling functions, * such as return drush_set_error('DRUSH_FRAMEWORK_ERROR') */ function drush_set_error($error, $message = null, $output_label = "") { $error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS); $error_code = DRUSH_FRAMEWORK_ERROR; $error_log =& drush_get_context('DRUSH_ERROR_LOG', array()); if (is_numeric($error)) { $error = 'DRUSH_FRAMEWORK_ERROR'; } $message = ($message) ? $message : drush_command_invoke_all('drush_help', 'error:' . $error); if (is_array($message)) { $message = implode("\n", $message); } $error_log[$error][] = $message; if (!drush_backend_packet('set_error', array('error' => $error, 'message' => $message))) { drush_log(($message) ? $output_label . $message : $output_label . $error, 'error', $error); } return FALSE; } /** * Return the current error handling status * * @return * The current aggregate error status */ function drush_get_error() { return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS); } /** * Return the current list of errors that have occurred. * * @return * An associative array of error messages indexed by the type of message. */ function drush_get_error_log() { return drush_get_context('DRUSH_ERROR_LOG', array()); } /** * Check if a specific error status has been set. * * @param error * A text string identifying the error that has occurred. * @return * TRUE if the specified error has been set, FALSE if not */ function drush_cmp_error($error) { $error_log = drush_get_error_log(); if (is_numeric($error)) { $error = 'DRUSH_FRAMEWORK_ERROR'; } return array_key_exists($error, $error_log); } /** * Clear error context. */ function drush_clear_error() { drush_set_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS); } /** * Exit due to user declining a confirmation prompt. * * Usage: return drush_user_abort(); */ function drush_user_abort($msg = NULL) { drush_set_context('DRUSH_USER_ABORT', TRUE); drush_log($msg ? $msg : dt('Aborting.'), 'cancel'); return FALSE; } /** * Turn PHP error handling off. * * This is commonly used while bootstrapping Drupal for install * or updates. * * This also records the previous error_reporting setting, in * case it wasn't recorded previously. * * @see drush_errors_off() */ function drush_errors_off() { drush_get_context('DRUSH_ERROR_REPORTING', error_reporting(0)); ini_set('display_errors', FALSE); } /** * Turn PHP error handling on. * * We default to error_reporting() here just in * case drush_errors_on() is called before drush_errors_off() and * the context is not yet set. * * @arg $errors string * The default error level to set in drush. This error level will be * carried through further drush_errors_on()/off() calls even if not * provided in later calls. * * @see error_reporting() * @see drush_errors_off() */ function drush_errors_on($errors = null) { if (is_null($errors)) { $errors = error_reporting(); } else { drush_set_context('DRUSH_ERROR_REPORTING', $errors); } error_reporting(drush_get_context('DRUSH_ERROR_REPORTING', $errors)); ini_set('display_errors', TRUE); } /** * @} End of "defgroup errorhandling". */ /** * Get the PHP memory_limit value in bytes. */ function drush_memory_limit() { $value = trim(ini_get('memory_limit')); $last = strtolower($value[strlen($value)-1]); switch ($last) { case 'g': $value *= DRUSH_DRUPAL_KILOBYTE; case 'm': $value *= DRUSH_DRUPAL_KILOBYTE; case 'k': $value *= DRUSH_DRUPAL_KILOBYTE; } return $value; } /** * Unset the named key anywhere in the provided * data structure. */ function drush_unset_recursive(&$data, $unset_key) { if (!empty($data) && is_array($data)) { unset($data[$unset_key]); foreach ($data as $key => $value) { if (is_array($value)) { drush_unset_recursive($data[$key], $unset_key); } } } } /** * Return a list of VCSs reserved files and directories. */ function drush_version_control_reserved_files() { static $files = FALSE; if (!$files) { // Also support VCSs that are not drush vc engines. $files = array('.git', '.gitignore', '.hg', '.hgignore', '.hgrags'); $engine_info = drush_get_engines('version_control'); $vcs = array_keys($engine_info['engines']); foreach ($vcs as $name) { $version_control = drush_include_engine('version_control', $name); $files = array_merge($files, $version_control->reserved_files()); } } return $files; } /** * Generate a random alphanumeric password. Copied from user.module. */ function drush_generate_password($length = 10) { // This variable contains the list of allowable characters for the // password. Note that the number 0 and the letter 'O' have been // removed to avoid confusion between the two. The same is true // of 'I', 1, and 'l'. $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Zero-based count of characters in the allowable list: $len = strlen($allowable_characters) - 1; // Declare the password as a blank string. $pass = ''; // Loop the number of times specified by $length. for ($i = 0; $i < $length; $i++) { // Each iteration, pick a random character from the // allowable string and append it to the password: $pass .= $allowable_characters[mt_rand(0, $len)]; } return $pass; } /** * Form an associative array from a linear array. * * This function walks through the provided array and constructs an associative * array out of it. The keys of the resulting array will be the values of the * input array. The values will be the same as the keys unless a function is * specified, in which case the output of the function is used for the values * instead. * * @param $array * A linear array. * @param $function * A name of a function to apply to all values before output. * * @return * An associative array. */ function drush_map_assoc($array, $function = NULL) { // array_combine() fails with empty arrays: // http://bugs.php.net/bug.php?id=34857. $array = !empty($array) ? array_combine($array, $array) : array(); if (is_callable($function)) { $array = array_map($function, $array); } return $array; } /** * Clears completion caches. * * If called with no parameters the entire complete cache will be cleared. * If called with just the $type parameter the global cache for that type will * be cleared (in the site context, if any). If called with both $type and * $command parameters the command cache of that type will be cleared (in the * site context, if any). * * This is included in drush.inc as complete.inc is only loaded conditionally. * * @param $type * The completion type (optional). * @param $command * The command name (optional), if command specific cache is to be cleared. * If specifying a command, $type is not optional. */ function drush_complete_cache_clear($type = NULL, $command = NULL) { require_once DRUSH_BASE_PATH . '/includes/complete.inc'; if ($type) { drush_cache_clear_all(drush_complete_cache_cid($type, $command), 'complete'); return; } // No type or command, so clear the entire complete cache. drush_cache_clear_all('*', 'complete', TRUE); } drush-5.10.0/includes/environment.inc000066400000000000000000000730771222105546100176030ustar00rootroot00000000000000 php_ini_loaded_file())); } else { 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.'); } } /** * Evalute the environment after an abnormal termination and * see if we can determine any configuration settings that the user might * want to adjust. */ function _drush_postmortem() { // Make sure that the memory limit has been bumped up from the minimum default value of 32M. $php_memory_limit = drush_memory_limit(); if (($php_memory_limit > 0) && ($php_memory_limit <= 32*DRUSH_DRUPAL_KILOBYTE*DRUSH_DRUPAL_KILOBYTE)) { 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_DRUPAL_KILOBYTE*DRUSH_DRUPAL_KILOBYTE) . 'M', '!php_ini_msg' => _drush_php_ini_loaded_file_message()))); } } /** * Evaluate the environment before command bootstrapping * begins. If the php environment is too restrictive, then * notify the user that a setting change is needed and abort. */ function _drush_environment_check_php_ini() { $ini_checks = array('safe_mode' => '', 'open_basedir' => '', 'disable_functions' => array('exec', 'system'), 'disable_classes' => ''); // Test to insure that certain php ini restrictions have not been enabled $prohibited_list = array(); foreach ($ini_checks as $prohibited_mode => $disallowed_value) { $ini_value = ini_get($prohibited_mode); $invalid_value = FALSE; if (empty($disallowed_value)) { $invalid_value = !empty($ini_value) && (strcasecmp($ini_value, 'off') != 0); } else { foreach ($disallowed_value as $test_value) { if (strstr($ini_value, $test_value) !== FALSE) { $invalid_value = TRUE; } } } if ($invalid_value) { $prohibited_list[] = $prohibited_mode; } } if (!empty($prohibited_list)) { 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())), 'error'); } return TRUE; } /* * Check for the existence of the specified lib directory, and create if needed. */ function drush_environment_lib() { $lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib'); // We tell drush_mkdir that $lib is not required, because otherwise it // will throw an error if the folder exists but is not writable. It // is okay with us if the $lib dir is not writable by the current // user, as it may have already been set up by the user who installed Drush. drush_mkdir($lib, FALSE); if (!is_dir($lib)) { return FALSE; } } function drush_environment_table_lib() { // Try using the PEAR installed version of Console_Table. $tablefile = 'Console/Table.php'; if (@file_get_contents($tablefile, FILE_USE_INCLUDE_PATH) === FALSE) { $lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib'); $tablefile = $lib . '/Console_Table-' . DRUSH_TABLE_VERSION . '/Table.php'; // If it is not already present, download Console Table. if (!drush_file_not_empty($tablefile)) { // Attempt to remove the old Console Table file, from the legacy location. // TODO: Remove this (and associated .git.ignore) in Drush 6.x. $tablefile_legacy = DRUSH_BASE_PATH . '/includes/table.inc'; if (drush_file_not_empty($tablefile_legacy)) { drush_op('unlink', $tablefile_legacy); } // Download and extract Console_Table, and confirm success. if (drush_lib_fetch(DRUSH_PEAR_BASE_URL . 'Console_Table-' . DRUSH_TABLE_VERSION . '.tgz')) { // Remove unneccessary package.xml file which ends up in /lib. drush_op('unlink', $lib . '/package.xml'); } if (!drush_file_not_empty($tablefile)) { return drush_bootstrap_error('DRUSH_TABLES_LIB_NOT_FOUND', dt("Drush needs a copy of the PEAR Console_Table library in order to function, and the attempt to download this file automatically failed. To continue you will need to download the !version package from http://pear.php.net/package/Console_Table, extract it into !lib directory, such that Table.php exists at !tablefile.", array('!version' => DRUSH_TABLE_VERSION, '!tablefile' => $tablefile, '!lib' => $lib))); } } } require_once $tablefile; } /** * Returns the current working directory. * * This is the directory as it was when drush was started, not the * directory we are currently in. For that, use getcwd() directly. */ function drush_cwd() { if ($path = drush_get_context('DRUSH_OLDCWD')) { return $path; } // We use PWD if available because getcwd() resolves symlinks, which // could take us outside of the Drupal root, making it impossible to find. // $_SERVER['PWD'] isn't set on windows and generates a Notice. $path = isset($_SERVER['PWD']) ? $_SERVER['PWD'] : ''; if (empty($path)) { $path = getcwd(); } // Convert windows paths. $path = _drush_convert_path($path); // Save original working dir case some command wants it. drush_set_context('DRUSH_OLDCWD', $path); return $path; } /** * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3). * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a * proper drive path, still with Unix slashes (c:/dir1). */ function _drush_convert_path($path) { $path = str_replace('\\','/', $path); $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path); return $path; } /** * Returns parent directory. * * @param string * Path to start from. * * @return string * Parent path of given path. */ function _drush_shift_path_up($path) { if (empty($path)) { return FALSE; } $path = explode('/', $path); // Move one directory up. array_pop($path); return implode('/', $path); } /** * Like Drupal conf_path, but searching from beneath. * Allows proper site uri detection in site sub-directories. * * Essentially looks for a settings.php file. Drush uses this * function to find a usable site based on the user's current * working directory. * * @param string * Search starting path. Defaults to current working directory. * * @return * Current site path (folder containing settings.php) or FALSE if not found. */ function drush_site_path($path = NULL) { $site_path = FALSE; $path = empty($path) ? drush_cwd() : $path; // Check the current path. if (file_exists($path . '/settings.php')) { $site_path = $path; } else { // Move up dir by dir and check each. while ($path = _drush_shift_path_up($path)) { if (file_exists($path . '/settings.php')) { $site_path = $path; break; } } } $site_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); if (file_exists($site_root . '/sites/sites.php')) { $sites = array(); // This will overwrite $sites with the desired mappings. include($site_root . '/sites/sites.php'); // We do a reverse lookup here to determine the URL given the site key. if ($match = array_search($site_path, $sites)) { $site_path = $match; } } // Last resort: try from site root if (!$site_path) { if ($site_root) { if (file_exists($site_root . '/sites/default/settings.php')) { $site_path = $site_root . '/sites/default'; } } } return $site_path; } /** * This is a copy of Drupal's conf_path function, taken from D7 and * adjusted slightly to search from the selected Drupal Root. * * Drush uses this routine to find a usable site based on a URI * passed in via a site alias record or the --uri commandline option. * * Drush uses Drupal itself (specifically, the Drupal conf_path function) * to bootstrap the site itself. If the implementation of conf_path * changes, the site should still bootstrap correctly; the only consequence * of this routine not working is that drush configuration files * (drushrc.php) stored with the site's settimight not be found. */ function drush_conf_path($server_uri, $require_settings = TRUE) { $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); if(empty($drupal_root) || empty($server_uri)) { return NULL; } $parsed_uri = parse_url($server_uri); if (is_array($parsed_uri) && !array_key_exists('scheme', $parsed_uri)) { $parsed_uri = parse_url('http://' . $server_uri); } if (!is_array($parsed_uri)) { return NULL; } $server_host = $parsed_uri['host']; if (array_key_exists('path', $parsed_uri)) { $server_uri = $parsed_uri['path'] . '/index.php'; } else { $server_uri = "/index.php"; } $confdir = 'sites'; $sites = array(); if (file_exists($drupal_root . '/' . $confdir . '/sites.php')) { // This will overwrite $sites with the desired mappings. include($drupal_root . '/' . $confdir . '/sites.php'); } $uri = explode('/', $server_uri); $server = explode('.', implode('.', array_reverse(explode(':', rtrim($server_host, '.'))))); for ($i = count($uri) - 1; $i > 0; $i--) { for ($j = count($server); $j > 0; $j--) { $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); if (isset($sites[$dir]) && file_exists($drupal_root . '/' . $confdir . '/' . $sites[$dir])) { $dir = $sites[$dir]; } if (file_exists($drupal_root . '/' . $confdir . '/' . $dir . '/settings.php') || (!$require_settings && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir))) { $conf = "$confdir/$dir"; return $conf; } } } $conf = "$confdir/default"; return $conf; } /** * Exhaustive depth-first search to try and locate the Drupal root directory. * This makes it possible to run drush from a subdirectory of the drupal root. * * @param * Search start path. Defaults to current working directory. * @return * A path to drupal root, or FALSE if not found. */ function drush_locate_root($start_path = NULL) { $drupal_root = FALSE; $start_path = empty($start_path) ? drush_cwd() : $start_path; foreach (array(TRUE, FALSE) as $follow_symlinks) { $path = $start_path; if ($follow_symlinks && is_link($path)) { $path = realpath($path); } // Check the start path. if (drush_valid_drupal_root($path)) { $drupal_root = $path; break; } else { // Move up dir by dir and check each. while ($path = _drush_shift_path_up($path)) { if ($follow_symlinks && is_link($path)) { $path = realpath($path); } if (drush_valid_drupal_root($path)) { $drupal_root = $path; break 2; } } } } return $drupal_root; } /** * Checks whether given path qualifies as a Drupal root. * * @param string * Path to check. * * @return string * The relative path to common.inc (varies by Drupal version), or FALSE if not * a Drupal root. */ function drush_valid_drupal_root($path) { if (!empty($path) && is_dir($path)) { $candidates = array('includes/common.inc', 'core/includes/common.inc'); foreach ($candidates as $candidate) { if (file_exists($path . '/' . $candidate)) { return $candidate; } } } return FALSE; } /** * Tests the currently loaded database credentials to ensure a database connection can be made. */ function drush_valid_db_credentials() { $creds = drush_get_context('DRUSH_DB_CREDENTIALS'); // Do minimal checking that we have the necessary information. if (count($creds) == 0) { return FALSE; } $type = $creds['driver']; switch (drush_drupal_major_version()) { case 6: // Check availability of db extension in PHP and also Drupal support. if (file_exists('./includes/install.'. $type .'.inc')) { require_once './includes/install.'. $type .'.inc'; $function = $type .'_is_available'; if (!$function()) { drush_log(dt('!type extension for PHP is not installed. Check your php.ini to see how you can enable it.', array('!type' => $type)), 'bootstrap'); return FALSE; } } else { drush_log(dt('!type database type is unsupported.', array('!type' => $type)), 'bootstrap'); return FALSE; } // Verify connection settings. switch ($type) { case 'mysql': $hostspec = $creds['port'] ? $creds['host'] . ':' . $creds['port'] : $creds['host']; $connection = @mysql_connect($hostspec, $creds['user'], $creds['pass']); if (!$connection || !mysql_select_db($creds['name'])) { drush_log(mysql_error(), 'bootstrap'); return FALSE; } break; case 'mysqli': $connection = mysqli_init(); @mysqli_real_connect($connection, $creds['host'], $creds['user'], $creds['pass'], $creds['name'], (int)$creds['port']); if (mysqli_connect_errno() > 0) { drush_log(mysqli_connect_error(), 'bootstrap'); return FALSE; } break; case 'pgsql': $conn_string = sprintf("host=%s user=%s password=%s dbname=%s", $creds['host'], $creds['user'], $creds['pass'], $creds['name']); if (isset($creds['port'])) { $conn_string .= ' port=' . $creds['port']; } // Copied from d6's database.pgsql.inc: // pg_last_error() does not return a useful error message for database // connection errors. We must turn on error tracking to get at a good error // message, which will be stored in $php_errormsg. $php_errormsg = ''; $track_errors_previous = ini_get('track_errors'); ini_set('track_errors', 1); $connection = @pg_connect($conn_string); // Restore error tracking setting ini_set('track_errors', $track_errors_previous); if (!$connection) { if (empty($php_errormsg)) { drush_log(dt("Unknown error connecting to pgsql database via !constr", array('!constr' => $conn_string)), 'bootstrap'); } else { require_once './includes/unicode.inc'; drush_log(decode_entities($php_errormsg), 'bootstrap'); } return FALSE; } break; } break; case 7: default: $type = ( $type=='oracle' ? 'oci' : $type); // fix PDO driver name, should go away in Drupal 8 // Drupal >=7 requires PDO and drush requires php 5.2, that ships with PDO // but it may be compiled with --disable-pdo. if (!class_exists('PDO')) { drush_log(dt('PDO support is required.'), 'bootstrap'); return FALSE; } // Check the database specific driver is available. if (!in_array($type, PDO::getAvailableDrivers())) { drush_log(dt('!type extension for PHP PDO is not installed. Check your php.ini to see how you can enable it.', array('!type' => $type)), 'bootstrap'); return FALSE; } // Build the connection string. if ($type === 'sqlite') { $constr = 'sqlite:' . $creds['name']; } elseif ($type === 'sqlsrv') { $server = $creds['host']; if (!empty($creds['port'])) { $server .= ", " . $creds['port']; } $constr = sprintf("%s:database=%s;server=%s", $type, $creds['name'], $server); } elseif ($type === 'oci') { if (empty($creds['port'])) $creds['port'] = 1521; if ($creds['host'] == 'USETNS') $constr = 'oci:dbname=' . $creds['database'] . ';charset=AL32UTF8'; else $constr = 'oci:dbname=//' . $creds['host'] . ':' . $creds['port'] . '/' . $creds['database'] . ';charset=AL32UTF8'; } else { $constr = sprintf("%s:dbname=%s", $type, $creds['name']); // Use unix_socket if set instead of host:port. if (!empty($creds['unix_socket'])) { $constr .= sprintf(";unix_socket=%s", $creds['unix_socket']); } else { $constr .= sprintf(";host=%s", $creds['host']); if (!empty($creds['port'])) { $constr .= sprintf(";port=%d", $creds['port']); } } } try { $db = new PDO($constr, $creds['user'], $creds['pass']); $db = null; } catch (PDOException $e) { drush_log($e->getMessage(), 'bootstrap'); return FALSE; } break; } return TRUE; } /** * Determine a proper way to call drush again * * This check if we were called directly or as an argument to some * wrapper command (php and sudo are checked now). * * Calling ./drush.php directly yields the following environment: * * _SERVER["argv"][0] => ./drush.php * * Calling php ./drush.php also yields the following: * * _SERVER["argv"][0] => ./drush.php * * Note that the $_ global is defined only in bash and therefore cannot * be relied upon. * * The DRUSH_COMMAND constant is initialised to the value of this * function when environment.inc is loaded. * * @see DRUSH_COMMAND */ function drush_find_drush() { $drush = realpath($_SERVER['argv']['0']); // TODO: On Windows, if we leave $drush as-is, then callbacks will // be done just as we were called by the batch file: php.exe C:\path\drush.php // We could also convert drush.php to drush.bat to run the batch file again, // but this works just as well. return $drush; } /** * Verify that we are running PHP through the command line interface. * * This function is useful for making sure that code cannot be run via the web server, * such as a function that needs to write files to which the web server should not have * access to. * * @return * A boolean value that is true when PHP is being run through the command line, * and false if being run through cgi or mod_php. */ function drush_verify_cli() { return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)); } /** * Build a drush command suitable for use for drush to call itself * e.g. in backend_invoke. */ function drush_build_drush_command($drush_path = NULL, $php = NULL, $os = NULL, $remote_command = FALSE) { $os = _drush_get_os($os); $additional_options = ''; if (is_null($drush_path)) { if (!$remote_command) { $drush_path = DRUSH_COMMAND; } else { $drush_path = drush_is_windows($os) ? 'drush.bat' : 'drush'; } } // If the path to drush points to drush.php, then we will need to // run it via php rather than direct execution. By default, we // will use 'php' unless something more specific was passed in // via the --php flag. if (substr($drush_path, -4) == ".php") { if (!isset($php)) { $php = drush_get_option('php'); if (!isset($php)) { $php = 'php'; } } if (isset($php) && ($php != "php")) { $additional_options .= ' --php=' . drush_escapeshellarg($php, $os); } // We will also add in the php options from --php-options $php = drush_escapeshellarg($php, $os) . ' '; $php_options = drush_get_option('php-options',''); if (!empty($php_options)) { $php .= $php_options . ' '; $additional_options .= ' --php-options=' . drush_escapeshellarg($php_options, $os); } } else { $php = ''; } return $php . drush_escapeshellarg($drush_path, $os) . $additional_options; } /** * Check if the operating system is Windows. * This will return TRUE under DOS, Powershell * Cygwin and MSYSGIT shells, so test for the * Windows variant FIRST if you care. */ function drush_is_windows($os = NULL) { return _drush_test_os($os, array("WIN","CYGWIN","CWRSYNC","MINGW")); } /** * Check if the operating system is Winodws * running some variant of cygwin -- either * Cygwin or the MSYSGIT shell. If you care * which is which, test mingw first. */ function drush_is_cygwin($os = NULL) { return _drush_test_os($os, array("CYGWIN","CWRSYNC","MINGW")); } function drush_is_mingw($os = NULL) { return _drush_test_os($os, array("MINGW")); } /* * Return tar executable name specific for the current OS */ function drush_get_tar_executable() { return drush_is_windows() ? (drush_is_mingw() ? "tar.exe" : "bsdtar.exe") : "tar"; } /** * Check if the operating system is OS X. * This will return TRUE for Mac OS X (Darwin). */ function drush_is_osx($os = NULL) { return _drush_test_os($os, array("DARWIN")); } /** * Checks if the operating system has bash. * * MinGW has bash, but PHP isn't part of MinGW and hence doesn't run in bash. */ function drush_has_bash($os = NULL) { return (drush_is_cygwin($os) && !drush_is_mingw($os)) || !drush_is_windows($os); } /* * Checks operating system and returns * supported bit bucket folder. */ function drush_bit_bucket() { if (drush_has_bash()) { return '/dev/null'; } else { return 'nul'; } } /** * Return the OS we are running under. * * @return string * Linux * WIN* (e.g. WINNT) * CYGWIN * MINGW* (e.g. MINGW32) */ function _drush_get_os($os = NULL) { // The special os "CWRSYNC" can be used to indicate that we are testing // a path that will be passed as an argument to cwRsync, which requires // that the path be converted to /cygdrive/c/path, even on DOS or Powershell. // The special os "RSYNC" can be used to indicate that we want to assume // "CWRSYNC" when cwrsync is installed, or default to the local OS otherwise. if (strtoupper($os) == "RSYNC") { $os = _drush_get_os("LOCAL"); // For now we assume that cwrsync is always installed on Windows, and never installed son any other platform. return drush_is_windows($os) ? "CWRSYNC" : $os; } // We allow "LOCAL" to document, in instances where some parameters are being escaped // for use on a remote machine, that one particular parameter will always be used on // the local machine (c.f. drush_backend_invoke). if (isset($os) && ($os != "LOCAL")) { return $os; } if (_drush_test_os(getenv("MSYSTEM"), array("MINGW"))) { return getenv("MSYSTEM"); } // QUESTION: Can we differentiate between DOS and POWERSHELL? They appear to have the same environment. // At the moment, it does not seem to matter; they behave the same from PHP. // At this point we will just return PHP_OS. return PHP_OS; } function _drush_test_os($os, $os_list_to_check) { $os = _drush_get_os($os); foreach ($os_list_to_check as $test) { if (strtoupper(substr($os, 0, strlen($test))) == strtoupper($test)) { return TRUE; } } return FALSE; } /** * Read the drush info file. */ function drush_read_drush_info() { $drush_info_file = dirname(__FILE__) . '/../drush.info'; return parse_ini_file($drush_info_file); } /** * Make a determination whether or not the given * host is local or not. * * @param host * A hostname, 'localhost' or '127.0.0.1'. * @return * True if the host is local. */ function drush_is_local_host($host) { // In order for this to work right, you must use 'localhost' or '127.0.0.1' // or the machine returned by 'uname -n' for your 'remote-host' entry in // your site alias. if (($host == 'localhost') || ($host == '127.0.0.1')) { return TRUE; } // We'll start out by asking for uname -n via php_uname, since // that is most portable. On Linux, uname -n returns the contents of // /etc/hostname, which, according to `man hostname`, should never // contain the fqdn. We will therefore also try `hostname -f`, and // see if we can get the fqdn from that. $uname = php_uname('n'); if ((strpos($uname,'.') === FALSE) && (!drush_is_windows())) { $hostname = exec('hostname -f'); if (!empty($hostname)) { $uname = $hostname; } } // We will require an exact match if we have the fqdn for this // host; if we only have the short name, then we will consider it // to be a match if it matches the first part of the remote-host // item (up to the first dot). return (strpos($uname,'.') !== FALSE) ? ($host == $uname) : substr($host . '.',0,strlen($uname)+1) == $uname . '.'; } /** * Return the user's home directory. */ function drush_server_home() { // Cannot use $_SERVER superglobal since that's empty during Drush_UnitTestCase // getenv('HOME') isn't set on windows and generates a Notice. $home = getenv('HOME'); if (empty($home)) { if (!empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH'])) { // home on windows $home = $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH']; } } return empty($home) ? NULL : $home; } /** * Return the name of the user running drush. */ function drush_get_username() { return drush_is_windows() ? getenv("username") : getenv("USER"); } /** * The path to the global cache directory. * * @param subdir * Return the specified subdirectory inside the global * cache directory instead. The subdirectory is created. */ function drush_directory_cache($subdir = '') { $cache_locations = array(); if (getenv('CACHE_PREFIX')) { $cache_locations[getenv('CACHE_PREFIX')] = 'cache'; } $home = drush_server_home(); if ($home) { $cache_locations[$home] = '.drush/cache'; } $cache_locations[drush_find_tmp()] = 'drush-' . drush_get_username() . '/cache'; foreach ($cache_locations as $base => $dir) { if (!empty($base) && is_writable($base)) { $cache_dir = $base . '/' . $dir; if (!empty($subdir)) { $cache_dir .= '/' . $subdir; } if (drush_mkdir($cache_dir)) { return $cache_dir; } else { // If the base directory is writable, but the cache directory // is not, then we will get an error. The error will be displayed, // but we will still call drush_clear_error so that we can go // on and try the next location to see if we can find a cache // directory somewhere. drush_clear_error(); } } } 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', array('@locations' => implode(',', array_keys($cache_locations))))); } /** * Get complete information for all available extensions (modules and themes). * * @return * An array containing info for all available extensions. */ function drush_get_extensions($include_hidden = TRUE) { drush_include_engine('drupal', 'environment'); $extensions = array_merge(drush_get_modules($include_hidden), drush_get_themes($include_hidden)); foreach ($extensions as $name => $extension) { if (isset($extension->info['name'])) { $extensions[$name]->label = $extension->info['name'].' ('.$extension->name.')'; } else { drush_log(dt("Extension !name provides no human-readable name in .info file.", array('!name' => $name), 'debug')); $extensions[$name]->label = $name.' ('.$extension->name.')'; } if (empty($extension->info['package'])) { $extensions[$name]->info['package'] = dt('Other'); } } return $extensions; } /** * Test compatibility of a extension with version of drupal core and php. * * @param $file Extension object as returned by system_rebuild_module_data(). * @return FALSE if the extension is compatible. */ function drush_extension_check_incompatibility($file) { if (!isset($file->info['core']) || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY) { return 'Drupal'; } if (version_compare(phpversion(), $file->info['php']) < 0) { return 'PHP'; } return FALSE; } /** * */ function drush_drupal_required_modules($modules) { drush_include_engine('drupal', 'environment'); return _drush_drupal_required_modules($modules); } /** * Return the default theme. * * @return * Machine name of the default theme. */ function drush_theme_get_default() { return variable_get('theme_default', 'garland'); } /** * Return the administration theme. * * @return * Machine name of the administration theme. */ function drush_theme_get_admin() { return variable_get('admin_theme', drush_theme_get_default()); } drush-5.10.0/includes/exec.inc000066400000000000000000000353721222105546100161570ustar00rootroot00000000000000&1', $output, $result); _drush_shell_exec_output_set($output); if (drush_get_context('DRUSH_DEBUG')) { foreach ($output as $line) { drush_print($line, 2); } } // Exit code 0 means success. return ($result == 0); } } else { return TRUE; } } /** * Build an SSH string including an optional fragment of bash. Commands that use * this should also merge drush_shell_proc_build_options() into their * command options. @see ssh_drush_command(). * * @param array $site * A site alias record. * @param string $command * An optional bash fragment. * @param string $cd * An optional directory to change into before executing the $command. Set to * boolean TRUE to change into $site['root'] if available. * @return string * A string suitable for execution with drush_shell_remote_exec(). */ function drush_shell_proc_build($site, $command = '', $cd = NULL) { // Build up the command. TODO: We maybe refactor this soon. $hostname = drush_remote_host($site); $ssh_options = drush_sitealias_get_option($site, 'ssh-options', "-o PasswordAuthentication=no"); $os = drush_os($site); if (drush_sitealias_get_option($site, 'tty')) { $ssh_options .= ' -t'; } $cmd = "ssh " . $ssh_options . " " . $hostname; if ($cd === TRUE) { if (array_key_exists('root', $site)) { $cd = $site['root']; } else { $cd = FALSE; } } if ($cd) { $command = 'cd ' . drush_escapeshellarg($cd, $os) . ' && ' . $command; } if (!empty($command)) { if (!drush_get_option('escaped', FALSE)) { $cmd .= " " . drush_escapeshellarg($command, $os); } else { $cmd .= " $command"; } } return $cmd; } /* * Execute bash command using proc_open(). * * @returns * Exit code from launched application * 0 no error * 1 general error * 127 command not found */ function drush_shell_proc_open($cmd) { if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { drush_print("Calling proc_open($cmd);", 0, STDERR); } if (!drush_get_context('DRUSH_SIMULATE')) { $process = proc_open($cmd, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes); $proc_status = proc_get_status($process); $exit_code = proc_close($process); return ($proc_status["running"] ? $exit_code : $proc_status["exitcode"] ); } return 0; } /** * Used by definition of ssh and other commands that call into drush_shell_proc_build() * to declare their options. */ function drush_shell_exec_proc_build_options() { return array( 'ssh-options' => 'A string of extra options that will be passed to the ssh command (e.g. "-p 100")', 'tty' => 'Create a tty (e.g. to run an interactive program).', 'escaped' => 'Command string already escaped; do not add additional quoting.', ); } /** * Determine the appropriate os value for the * specified site record * * @returns * NULL for 'same as local machine', 'Windows' or 'Linux'. */ function drush_os($site_record = NULL) { // Default to $os = NULL, meaning 'same as local machine' $os = NULL; // If the site record has an 'os' element, use it if (isset($site_record) && array_key_exists('os', $site_record)) { $os = $site_record['os']; } // Otherwise, we will assume that all remote machines are Linux // (or whatever value 'remote-os' is set to in drushrc.php). elseif (isset($site_record) && array_key_exists('remote-host', $site_record) && !empty($site_record['remote-host'])) { $os = drush_get_option('remote-os', 'Linux'); } return $os; } /** * Determine the remote host (username@hostname.tld) for * the specified site. */ function drush_remote_host($site, $prefix = '') { $hostname = drush_escapeshellarg(drush_sitealias_get_option($site, 'remote-host', '', $prefix), "LOCAL"); $username = drush_escapeshellarg(drush_sitealias_get_option($site, 'remote-user', '', $prefix), "LOCAL"); return $username . (empty($username) ? '' : '@') . $hostname; } /** * Make an attempt to simply wrap the arg with the * kind of quote characters it does not already contain. * If it contains both kinds, or if it contains no quote * characters, then this function reverts to drush_escapeshellarg. * * Note that this routine is only useful in certain very * specific circumstances (e.g. core-cli), as in general, * Windows -must- use double-quotes to escape a shell arg. */ function drush_wrap_with_quotes($arg) { $has_double = strpos($arg, '"') !== FALSE; $has_single = strpos($arg, "'") !== FALSE; // If there are both kinds of quotes ($has_double == TRUE && $has_single == TRUE) // or there are neither kind of quotes ($has_double == FALSE && $has_single == FALSE) // then we call drush_escapeshellarg. The relations above logically reduce to: if ($has_double == $has_single) { return drush_escapeshellarg($arg); } elseif ($has_double) { return "'" . $arg . "'"; } else { return '"' . $arg . '"'; } } /** * Platform-dependent version of escapeshellarg(). * Given the target platform, return an appropriately-escaped * string. The target platform may be omitted for args that * are /known/ to be for the local machine. */ function drush_escapeshellarg($arg, $os = NULL) { // Short-circuit escaping for simple params (keep stuff readable) if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { return $arg; } elseif (drush_is_windows($os)) { return _drush_escapeshellarg_windows($arg); } else { return _drush_escapeshellarg_linux($arg); } } /** * Windows version of escapeshellarg(). */ function _drush_escapeshellarg_windows($arg) { // Double up existing backslashes $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); // Double up double quotes $arg = preg_replace('/"/', '""', $arg); // Double up percents. $arg = preg_replace('/%/', '%%', $arg); // Add surrounding quotes. $arg = '"' . $arg . '"'; return $arg; } /** * Linux version of escapeshellarg(). * * This is intended to work the same way that escapeshellarg() does on * Linux. If we need to escape a string that will be used remotely on * a Linux system, then we need our own implementation of escapeshellarg, * because the Windows version behaves differently. */ function _drush_escapeshellarg_linux($arg) { // For single quotes existing in the string, we will "exit" // single-quote mode, add a \' and then "re-enter" // single-quote mode. The result of this is that // 'quote' becomes '\''quote'\'' $arg = preg_replace('/\'/', '\'\\\'\'', $arg); // Replace "\t", "\n", "\r", "\0", "\x0B" with a whitespace. // Note that this replacement makes Drush's escapeshellarg work differently // than the built-in escapeshellarg in PHP on Linux, as these characters // usually are NOT replaced. However, this was done deliberately to be more // conservative when running _drush_escapeshellarg_linux on Windows // (this can happen when generating a command to run on a remote Linux server.) $arg = str_replace(array("\t", "\n", "\r", "\0", "\x0B"), ' ', $arg); // Add surrounding quotes. $arg = "'" . $arg . "'"; return $arg; } /** * Stores output for the most recent shell command. * This should only be run from drush_shell_exec(). * * @param $output * The output of the most recent shell command. * If this is not set the stored value will be returned. */ function _drush_shell_exec_output_set($output = FALSE) { static $stored_output; if ($output === FALSE) return $stored_output; $stored_output = $output; } /** * Returns the output of the most recent shell command as an array of lines. */ function drush_shell_exec_output() { return _drush_shell_exec_output_set(); } /** * Starts a background browser/tab for the current site or a specified URL. * * Uses a non-blocking proc_open call, so Drush execution will continue. * * @param $uri * Optional URI or site path to open in browser. If omitted, or if a site path * is specified, the current site home page uri will be prepended if the sites * hostname resolves. * @return * TRUE if browser was opened, FALSE if browser was disabled by the user or a, * default browser could not be found. */ function drush_start_browser($uri = NULL, $sleep = FALSE) { if ($browser = drush_get_option('browser', TRUE)) { // We can only open a browser if we have a DISPLAY environment variable on // POSIX or are running Windows or OS X. if (!drush_get_context('DRUSH_SIMULATE') && !getenv('DISPLAY') && !drush_is_windows() && !drush_is_osx()) { drush_log(dt('No graphical display appears to be available, not starting browser.'), 'notice'); return FALSE; } $host = parse_url($uri, PHP_URL_HOST); if (!$host) { // Build a URI for the current site, if we were passed a path. $site = drush_get_context('DRUSH_URI'); $host = parse_url($site, PHP_URL_HOST); $uri = $site . '/' . ltrim($uri, '/'); } // Validate that the host part of the URL resolves, so we don't attempt to // open the browser for http://default or similar invalid hosts. $hosterror = (gethostbynamel($host) === FALSE); $iperror = (ip2long($host) && gethostbyaddr($host) == $host); if (!drush_get_context('DRUSH_SIMULATE') && ($hosterror || $iperror)) { drush_log(dt('!host does not appear to be a resolvable hostname or IP, not starting browser. You may need to use the --uri option in your command or site alias to indicate the correct URL of this site.', array('!host' => $host)), 'warning'); return FALSE; } if ($browser === TRUE) { // See if we can find an OS helper to open URLs in default browser. if (drush_shell_exec('which xdg-open')) { $browser = 'xdg-open'; } else if (drush_shell_exec('which open')) { $browser = 'open'; } else if (!drush_has_bash()) { $browser = 'start'; } else { // Can't find a valid browser. $browser = FALSE; } } $prefix = ''; if ($sleep) { $prefix = 'sleep ' . $sleep . ' && '; } if ($browser) { drush_log(dt('Opening browser !browser at !uri', array('!browser' => $browser, '!uri' => $uri))); if (!drush_get_context('DRUSH_SIMULATE')) { $pipes = array(); proc_close(proc_open($prefix . $browser . ' ' . drush_escapeshellarg($uri) . ' 2> ' . drush_bit_bucket() . ' &', array(), $pipes)); } return TRUE; } } return FALSE; } /** * @} End of "defgroup commandwrappers". */ drush-5.10.0/includes/filesystem.inc000066400000000000000000000540311222105546100174100ustar00rootroot00000000000000filename), $sum); $hashes[] = trim(str_replace(array($dir), array(''), $sum[0])); } sort($hashes); return md5(implode("\n", $hashes)); } /** * Deletes the provided file or folder and everything inside it. * * Usually respects read-only files and folders. To do a forced delete use * drush_delete_tmp_dir() or set the parameter $forced. * * @param $dir * The file or directory to delete. * @param $force * Try whatever possible to delete the directory - also read-only ones. * @param $follow_symlinks * Do not delete symlinked files. Simply unlink symbolic links. * @return * FALSE on failure, TRUE if everything was deleted. */ function drush_delete_dir($dir, $force = FALSE, $follow_symlinks = FALSE) { // Do not delete symlinked files, only unlink symbolic links if (is_link($dir) && !$follow_symlinks) { return unlink($dir); } // Allow to delete symlinks even if the target doesn't exist. if (!is_link($dir) && !file_exists($dir)) { return TRUE; } if (!is_dir($dir)) { if ($force) { // Force deletion of items with readonly flag. @chmod($dir, 0777); } return unlink($dir); } foreach (scandir($dir) as $item) { if ($item == '.' || $item == '..') { continue; } if ($force) { @chmod($dir, 0777); } if (!drush_delete_dir($dir . '/' . $item, $force)) { return FALSE; } } if ($force) { // Force deletion of items with readonly flag. @chmod($dir, 0777); } return rmdir($dir); } /** * Deletes the provided file or folder and everything inside it. * This function explicitely tries to delete read-only files / folders. * * @param $dir * The directory to delete * @return * FALSE on failure, TRUE if everything was deleted */ function drush_delete_tmp_dir($dir) { return drush_delete_dir($dir, TRUE); } /** * Copy $src to $dest. * * @param $src * The directory to copy. * @param $dest * The destination to copy the source to, including the new name of * the directory. To copy directory "a" from "/b" to "/c", then * $src = "/b/a" and $dest = "/c/a". To copy "a" to "/c" and rename * it to "d", then $dest = "/c/d". * @param $overwrite * Action to take if destination already exists. * - FILE_EXISTS_OVERWRITE - completely removes existing directory. * - FILE_EXISTS_ABORT - aborts the operation. * - FILE_EXISTS_MERGE - Leaves existing files and directories in place. * @return * TRUE on success, FALSE on failure. */ function drush_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) { // Preflight based on $overwrite if $dest exists. if (file_exists($dest)) { if ($overwrite === FILE_EXISTS_OVERWRITE) { drush_op('drush_delete_dir', $dest, TRUE); } elseif ($overwrite === FILE_EXISTS_ABORT) { return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest))); } elseif ($overwrite === FILE_EXISTS_MERGE) { // $overwrite flag may indicate we should merge instead. drush_log(dt('Merging existing !dest directory', array('!dest' => $dest))); } } // $src readable? if (!is_readable($src)) { return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src))); } // $dest writable? if (!is_writable(dirname($dest))) { return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest)))); } // Try to do a recursive copy. if (@drush_op('_drush_recursive_copy', $src, $dest)) { return TRUE; } return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest))); } /** * Internal function called by drush_copy_dir; do not use directly. */ function _drush_recursive_copy($src, $dest) { // all subdirectories and contents: if(is_dir($src)) { if (!drush_mkdir($dest, TRUE)) { return FALSE; } $dir_handle = opendir($src); while($file = readdir($dir_handle)) { if ($file != "." && $file != "..") { if (_drush_recursive_copy("$src/$file", "$dest/$file") !== TRUE) { return FALSE; } } } closedir($dir_handle); } elseif (is_link($src)) { symlink(readlink($src), $dest); } elseif (!(copy($src, $dest) && touch($dest, filemtime($src)))) { return FALSE; } // Preserve execute permission. if (!is_link($src) && !drush_is_windows()) { // Get execute bits of $src. $execperms = fileperms($src) & 0111; // Apply execute permissions if any. if ($execperms > 0) { $perms = fileperms($dest) | $execperms; chmod($dest, $perms); } } return TRUE; } /** * Move $src to $dest. * * If the php 'rename' function doesn't work, then we'll do copy & delete. * * @param $src * The directory to move. * @param $dest * The destination to move the source to, including the new name of * the directory. To move directory "a" from "/b" to "/c", then * $src = "/b/a" and $dest = "/c/a". To move "a" to "/c" and rename * it to "d", then $dest = "/c/d" (just like php rename function). * @param $overwrite * If TRUE, the destination will be deleted if it exists. * @return * TRUE on success, FALSE on failure. */ function drush_move_dir($src, $dest, $overwrite = FALSE) { // Preflight based on $overwrite if $dest exists. if (file_exists($dest)) { if ($overwrite) { drush_op('drush_delete_dir', $dest, TRUE); } else { return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest))); } } // $src readable? if (!drush_op('is_readable', $src)) { return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src))); } // $dest writable? if (!drush_op('is_writable', dirname($dest))) { return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest)))); } // Try rename. It will fail if $src and $dest are not in the same partition. if (@drush_op('rename', $src, $dest)) { return TRUE; } // Eventually it will create an empty file in $dest. See // http://www.php.net/manual/es/function.rename.php#90025 elseif (is_file($dest)) { drush_op('unlink', $dest); } // If 'rename' fails, then we will use copy followed // by a delete of the source. if (drush_copy_dir($src, $dest)) { drush_op('drush_delete_dir', $src, TRUE); return TRUE; } return drush_set_error('DRUSH_MOVE_DIR_FAILURE', dt('Unable to move !src to !dest.', array('!src' => $src, '!dest' => $dest))); } /** * Cross-platform compatible helper function to recursively create a directory tree. * * @param path * Path to directory to create. * @param required * If TRUE, then drush_mkdir will call drush_set_error on failure. * * Callers should *always* do their own error handling after calling drush_mkdir. * If $required is FALSE, then a different location should be selected, and a final * error message should be displayed if no usable locations can be found. * @see drush_directory_cache(). * If $required is TRUE, then the execution of the current command should be * halted if the required directory cannot be created. */ function drush_mkdir($path, $required = TRUE) { if (!is_dir($path)) { if (drush_mkdir(dirname($path))) { if (@mkdir($path)) { return TRUE; } elseif (is_dir($path) && is_writable($path)) { // The directory was created by a concurrent process. return TRUE; } else { if (!$required) { return FALSE; } if (is_writable(dirname($path))) { return drush_set_error('DRUSH_CREATE_DIR_FAILURE', dt('Unable to create !dir.', array('!dir' => preg_replace('/\w+\/\.\.\//', '', $path)))); } else { return drush_set_error('DRUSH_PARENT_NOT_WRITABLE', dt('Unable to create !newdir in !dir. Please check directory permissions.', array('!newdir' => basename($path), '!dir' => realpath(dirname($path))))); } } } return FALSE; } else { if (!is_writable($path)) { if (!$required) { return FALSE; } return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Directory !dir exists, but is not writable. Please check directory permissions.', array('!dir' => realpath($path)))); } return TRUE; } } /** * Save a string to a temporary file. Does not depend on Drupal's API. * The temporary file will be automatically deleted when drush exits. * * @param string $data * @param string $suffix * Append string to filename. use of this parameter if is discouraged. @see * drush_tempnam(). * @return string * A path to the file. */ function drush_save_data_to_temp_file($data, $suffix = NULL) { static $fp; $file = drush_tempnam('drush_', NULL, $suffix); $fp = fopen($file, "w"); fwrite($fp, $data); $meta_data = stream_get_meta_data($fp); $file = $meta_data['uri']; fclose($fp); return $file; } /** * Returns the path to a temporary directory. * * This is a custom version of Drupal's file_directory_path(). * We can't directly rely on sys_get_temp_dir() as this * path is not valid in some setups for Mac, and we want to honor * an environment variable (used by tests). */ function drush_find_tmp() { static $temporary_directory; if (!isset($temporary_directory)) { $directories = array(); // Operating system specific dirs. if (drush_is_windows()) { // get user specific and operating system temp folders from system environment variables // see http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx?mfr=true $tempdir = getenv('TEMP'); if (isset($tempdir)) { $directories[] = $tempdir; } $tmpdir = getenv('TMP'); if (isset($tmpdir)) { $directories[] = $tmpdir; } $windir = getenv('WINDIR'); if (isset($windir)) { // WINDIR itself is not writable, but it always contains a /Temp dir, // which is the system-wide temporary directory on older versions. Newer // versions only allow system processes to use it. $directories[] = $windir . '/Temp'; } } else { $directories[] = '/tmp'; } // This function exists in PHP 5 >= 5.2.1, but drush // requires PHP 5 >= 5.2.0, so we check for it. if (function_exists('sys_get_temp_dir')) { $directories[] = sys_get_temp_dir(); } foreach ($directories as $directory) { if (is_dir($directory) && is_writable($directory)) { $temporary_directory = $directory; break; } } if (empty($temporary_directory)) { // If no directory has been found, create one in cwd. $temporary_directory = drush_cwd() . '/tmp'; drush_mkdir($temporary_directory, TRUE); if (!is_dir($directory)) { return drush_set_error('DRUSH_UNABLE_TO_CREATE_TMP_DIR', dt("Unable to create a temporary directory.")); } drush_register_file_for_deletion($temporary_directory); } } return $temporary_directory; } /** * Creates a temporary file, and registers it so that * it will be deleted when drush exits. Whenever possible, * drush_save_data_to_temp_file() should be used instead * of this function. * * @param string suffix * Append this suffix to the filename. Use of this parameter is discouraged as * it can break the guarantee of tempname(). See http://www.php.net/manual/en/function.tempnam.php#42052. * Originally added to support Oracle driver. */ function drush_tempnam($pattern, $tmp_dir = NULL, $suffix = '') { if ($tmp_dir == NULL) { $tmp_dir = drush_find_tmp(); } $tmp_file = tempnam($tmp_dir, $pattern); drush_register_file_for_deletion($tmp_file); return $tmp_file . $suffix; } /** * Creates a temporary directory and return its path. */ function drush_tempdir() { $tmp_dir = drush_trim_path(drush_find_tmp()); $tmp_dir .= '/' . 'drush_tmp_' . uniqid(time() . '_'); drush_mkdir($tmp_dir); drush_register_file_for_deletion($tmp_dir); return $tmp_dir; } /** * Any file passed in to this function will be deleted * when drush exits. */ function drush_register_file_for_deletion($file = NULL) { static $registered_files = array(); if (isset($file)) { if (empty($registered_files)) { register_shutdown_function('_drush_delete_registered_files'); } $registered_files[] = $file; } return $registered_files; } /** * Delete all of the registered temporary files. */ function _drush_delete_registered_files() { $files_to_delete = drush_register_file_for_deletion(); foreach ($files_to_delete as $file) { // We'll make sure that the file still exists, just // in case someone came along and deleted it, even // though they did not need to. if (file_exists($file)) { if (is_dir($file)) { drush_delete_dir($file, TRUE); } else { @chmod($dir, 0777); // Make file writeable unlink($file); } } } } /** * Decide where our backup directory should go * * @param string $subdir * The name of the desired subdirectory(s) under drush-backups. * Usually a database name. */ function drush_preflight_backup_dir($subdir = NULL) { $backup_dir = drush_get_context('DRUSH_BACKUP_DIR', drush_get_option('backup-location')); if (empty($backup_dir)) { // Try to use db name as subdir if none was provided. if (empty($subdir)) { $subdir = 'unknown'; if ($creds = drush_get_context('DRUSH_DB_CREDENTIALS')) { $subdir = $creds['name']; } } // Save the date to be used in the backup directory's path name. $date = gmdate('YmdHis', $_SERVER['REQUEST_TIME']); $backup_dir = drush_get_option('backup-dir', drush_server_home() . '/' . 'drush-backups'); $backup_dir = drush_trim_path($backup_dir) . '/' . $subdir . '/' . $date; drush_set_context('DRUSH_BACKUP_DIR', $backup_dir); } return $backup_dir; } /** * Prepare a backup directory */ function drush_prepare_backup_dir($subdir = NULL) { $backup_dir = drush_preflight_backup_dir($subdir); $backup_parent = dirname($backup_dir); $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); $drupal_root .= '/'; if ((!empty($drupal_root)) && (strpos($backup_parent, $drupal_root) === 0)) { return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('It\'s not allowed to store backups inside the Drupal root directory.')); } if (!file_exists($backup_parent)) { if (!drush_mkdir($backup_parent, TRUE)) { return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Unable to create backup directory !dir.', array('!dir' => $backup_parent))); } } if (!is_writable($backup_parent)) { return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Backup directory !dir is not writable.', array('!dir' => $backup_parent))); } if (!drush_mkdir($backup_dir, TRUE)) { return FALSE; } return $backup_dir; } /** * Test to see if a file exists and is not empty */ function drush_file_not_empty($file_to_test) { if (file_exists($file_to_test)) { clearstatcache(); $stat = stat($file_to_test); if ($stat['size'] > 0) { return TRUE; } } return FALSE; } /** * Finds all files that match a given mask in a given directory. * Directories and files beginning with a period are excluded; this * prevents hidden files and directories (such as SVN working directories * and GIT repositories) from being scanned. * * @param $dir * The base directory for the scan, without trailing slash. * @param $mask * The regular expression of the files to find. * @param $nomask * An array of files/directories to ignore. * @param $callback * The callback function to call for each match. * @param $recurse_max_depth * When TRUE, the directory scan will recurse the entire tree * starting at the provided directory. When FALSE, only files * in the provided directory are returned. Integer values * limit the depth of the traversal, with zero being treated * identically to FALSE, and 1 limiting the traversal to the * provided directory and its immediate children only, and so on. * @param $key * The key to be used for the returned array of files. Possible * values are "filename", for the path starting with $dir, * "basename", for the basename of the file, and "name" for the name * of the file without an extension. * @param $min_depth * Minimum depth of directories to return files from. * @param $include_dot_files * If TRUE, files that begin with a '.' will be returned if they * match the provided mask. If FALSE, files that begin with a '.' * will not be returned, even if they match the provided mask. * @param $depth * Current depth of recursion. This parameter is only used internally and should not be passed. * * @return * An associative array (keyed on the provided key) of objects with * "path", "basename", and "name" members corresponding to the * matching files. */ function drush_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse_max_depth = TRUE, $key = 'filename', $min_depth = 0, $include_dot_files = FALSE, $depth = 0) { $key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename'); $files = array(); if (is_string($dir) && is_dir($dir) && $handle = opendir($dir)) { while (FALSE !== ($file = readdir($handle))) { if (!in_array($file, $nomask) && (($include_dot_files && (!preg_match("/\.\+/",$file))) || ($file[0] != '.'))) { if (is_dir("$dir/$file") && (($recurse_max_depth === TRUE) || ($depth < $recurse_max_depth))) { // Give priority to files in this folder by merging them in after any subdirectory files. $files = array_merge(drush_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse_max_depth, $key, $min_depth, $include_dot_files, $depth + 1), $files); } elseif ($depth >= $min_depth && preg_match($mask, $file)) { // Always use this match over anything already set in $files with the same $$key. $filename = "$dir/$file"; $basename = basename($file); $name = substr($basename, 0, strrpos($basename, '.')); $files[$$key] = new stdClass(); $files[$$key]->filename = $filename; $files[$$key]->basename = $basename; $files[$$key]->name = $name; if ($callback) { drush_op($callback, $filename); } } } } closedir($handle); } return $files; } /** * Simple helper function to append data to a given file. * * @param string $file * The full path to the file to append the data to. * @param string $data * The data to append. * * @return boolean * TRUE on success, FALSE in case of failure to open or write to the file. */ function drush_file_append_data($file, $data) { if (!$fd = fopen($file, 'a+')) { drush_set_error(dt("ERROR: fopen(@file, 'ab') failed", array('@file' => $file))); return FALSE; } if (!fwrite($fd, $data)) { drush_set_error(dt("ERROR: fwrite(@file) failed", array('@file' => $file)) . '

' . $data);
    return FALSE;
  }
  return TRUE;
}

/**
 * @} End of "defgroup filesystemfunctions".
 */
drush-5.10.0/includes/output.inc000066400000000000000000000274171222105546100165740ustar00rootroot00000000000000' => '------------------------------------------------------------------------------',
    '
  • ' => ' * ', '

    ' => '===== ', '

    ' => ' =====', '

    ' => '---- ', '

    ' => ' ----', '

    ' => '::: ', '

    ' => ' :::', '
    ' => "\n", ); $text = str_replace(array_keys($replacements), array_values($replacements), $html); return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text)); } /** * Print a formatted table. * * @param $rows * The rows to print. * @param $header * If TRUE, the first line will be treated as table header. * @param $widths * The widths of each column (in characters) to use - if not specified this * will be determined automatically, based on a "best fit" algorithm. * @param $handle * File handle to write to. NULL will write * to standard output, STDERR will write to the standard * error. See http://php.net/manual/en/features.commandline.io-streams.php * @return $tbl * Use $tbl->getTable() to get the output from the return value. */ function drush_print_table($rows, $header = FALSE, $widths = array(), $handle = NULL) { $tbl = new Console_Table(CONSOLE_TABLE_ALIGN_LEFT , ''); $auto_widths = drush_table_column_autowidth($rows, $widths); // Do wordwrap on all cells. $newrows = array(); foreach ($rows as $rowkey => $row) { foreach ($row as $col_num => $cell) { $newrows[$rowkey][$col_num] = wordwrap($cell, $auto_widths[$col_num], "\n", TRUE); if (isset($widths[$col_num])) { $newrows[$rowkey][$col_num] = str_pad($newrows[$rowkey][$col_num], $widths[$col_num]); } } } if ($header) { $headers = array_shift($newrows); $tbl->setHeaders($headers); } $tbl->addData($newrows); $output = $tbl->getTable(); if (!stristr(PHP_OS, 'WIN')) { $output = str_replace("\r\n", PHP_EOL, $output); } drush_print($output, 0, $handle); return $tbl; } /** * Convert an associative array of key : value pairs into * a table suitable for processing by drush_print_table. * * @param $keyvalue_table * An associative array of key : value pairs. * @return * An array of arrays, where the keys from the input * array are stored in the first column, and the values * are stored in the third. A second colum is created * specifically to hold the ':' separator. */ function drush_key_value_to_array_table($keyvalue_table) { $table = array(); foreach ($keyvalue_table as $key => $value) { if (isset($value)) { $table[] = array($key, ' :', $value); } else { $table[] = array($key . ':', '', ''); } } return $table; } /** * Determine the best fit for column widths. * * @param $rows * The rows to use for calculations. * @param $widths * Manually specified widths of each column (in characters) - these will be * left as is. */ function drush_table_column_autowidth($rows, $widths) { $auto_widths = $widths; // First we determine the distribution of row lengths in each column. // This is an array of descending character length keys (i.e. starting at // the rightmost character column), with the value indicating the number // of rows where that character column is present. $col_dist = array(); foreach ($rows as $rowkey => $row) { foreach ($row as $col_num => $cell) { if (empty($widths[$col_num])) { $length = strlen($cell); while ($length > 0) { if (!isset($col_dist[$col_num][$length])) { $col_dist[$col_num][$length] = 0; } $col_dist[$col_num][$length]++; $length--; } } } } foreach ($col_dist as $col_num => $count) { // Sort the distribution in decending key order. krsort($col_dist[$col_num]); // Initially we set all columns to their "ideal" longest width // - i.e. the width of their longest column. $auto_widths[$col_num] = max(array_keys($col_dist[$col_num])); } // We determine what width we have available to use, and what width the // above "ideal" columns take up. $available_width = drush_get_context('DRUSH_COLUMNS', 80) - (count($auto_widths) * 2); $auto_width_current = array_sum($auto_widths); // If we need to reduce a column so that we can fit the space we use this // loop to figure out which column will cause the "least wrapping", // (relative to the other columns) and reduce the width of that column. while ($auto_width_current > $available_width) { $count = 0; $width = 0; foreach ($col_dist as $col_num => $counts) { // If we are just starting out, select the first column. if ($count == 0 || // OR: if this column would cause less wrapping than the currently // selected column, then select it. (current($counts) < $count) || // OR: if this column would cause the same amount of wrapping, but is // longer, then we choose to wrap the longer column (proportionally // less wrapping, and helps avoid triple line wraps). (current($counts) == $count && key($counts) > $width)) { // Select the column number, and record the count and current width // for later comparisons. $column = $col_num; $count = current($counts); $width = key($counts); } } if ($width <= 1) { // If we have reached a width of 1 then give up, so wordwrap can still progress. break; } // Reduce the width of the selected column. $auto_widths[$column]--; // Reduce our overall table width counter. $auto_width_current--; // Remove the corresponding data from the disctribution, so next time // around we use the data for the row to the left. unset($col_dist[$column][$width]); } return $auto_widths; } /** * Print the contents of a file. * * @param string $file * Full path to a file. */ function drush_print_file($file) { // Don't even bother to print the file in --no mode if (drush_get_context('DRUSH_NEGATIVE')) { return; } if ((substr($file,-4) == ".htm") || (substr($file,-5) == ".html")) { $tmp_file = drush_tempnam(basename($file)); file_put_contents($tmp_file, drush_html_to_text(file_get_contents($file))); $file = $tmp_file; } // Do not wait for user input in --yes or --pipe modes if (drush_get_context('DRUSH_PIPE')) { drush_print_pipe(file_get_contents($file)); } elseif (drush_get_context('DRUSH_AFFIRMATIVE')) { drush_print(file_get_contents($file)); } elseif (drush_shell_exec_interactive("less %s", $file)) { return; } elseif (drush_shell_exec_interactive("more %s", $file)) { return; } else { drush_print(file_get_contents($file)); } } /** * Converts a PHP variable into its Javascript equivalent. * * We provide a copy of D7's drupal_json_encode since this function is * unavailable on earlier versions of Drupal. * * @see drupal_json_decode() * @ingroup php_wrappers */ function drush_json_encode($var) { // json_encode() does not escape <, > and &, so we do it with str_replace(). return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), json_encode($var)); } /** * Converts an HTML-safe JSON string into its PHP equivalent. * * We provide a copy of D7's drupal_json_decode since this function is * unavailable on earlier versions of Drupal. * * @see drupal_json_encode() * @ingroup php_wrappers */ function drush_json_decode($var) { return json_decode($var, TRUE); } /** * @} End of "defgroup outputfunctions". */ drush-5.10.0/includes/sitealias.inc000066400000000000000000002244361222105546100172120ustar00rootroot00000000000000 $alias_record) { $adjusted_name = $alias_record['#name']; $hashpos = strpos($original_name, '#'); if ($hashpos !== FALSE) { $adjusted_name = substr($original_name, $hashpos); if (array_key_exists('remote-host', $alias_record)) { $adjusted_name = $alias_record['remote-host'] . $adjusted_name; } } $result[$adjusted_name] = $alias_record; } return $result; } /** * Given an array of site specifications, resolve each one in turn and * return an array of alias records. If you only want a single record, * it is preferable to simply call drush_sitealias_get_record directly. * * @param $site_specifications * One of: * A comma-separated list of site specifications: '@site1,@site2' * An array of site specifications: array('@site1','@site2') * An array of alias records: * array( * 'site1' => array('root' => ...), * 'site2' => array('root' => ...) * ) * An array of site specifications. * @see drush_sitealias_get_record() for the format of site specifications. * @return * An array of alias records */ function drush_sitealias_resolve_sitespecs($site_specifications, $alias_path_context = NULL) { $result_list = array(); if (!is_array($site_specifications)) { $site_specifications = explode(',', $site_specifications); } if (!empty($site_specifications)) { foreach ($site_specifications as $site) { if (is_array($site)) { $result_list[] = $site; } else { $alias_record = drush_sitealias_get_record($site, $alias_path_context); $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record)); } } } return $result_list; } /** * Returns TRUE if $alias is a valid format for an alias name. * * Mirrors the allowed formats shown below for drush_sitealias_get_record. */ function drush_sitealias_valid_alias_format($alias) { return ( (strpos($alias, ',') !== false) || ((strpos($alias, '@') === FALSE ? 0 : 1) + (strpos($alias, '/') === FALSE ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) || ($alias{0} == '#') || ($alias{0} == '@') ); return $alias{0} == '@'; } /** * Get a site alias record given an alias name or site specification. * * If it is the name of a site alias, return the alias record from * the site aliases array. * * If it is the name of a folder in the 'sites' folder, construct * an alias record from values stored in settings.php. * * If it is a site specification, construct an alias record from the * values in the specification. * * Site specifications come in several forms: * - /path/to/drupal#sitename * - user@server/path/to/drupal#sitename * - user@server/path/to/drupal (sitename == server) * - user@server#sitename (only if $option['r'] set in some drushrc file on server) * - #sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites) * - sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites) * * Note that in the case of the first four forms, it is also possible * to add additional site variable to the specification using uri query * syntax. For example: * * user@server/path/to/drupal?db-url=...#sitename * * @param alias * An alias name or site specification * @return array * An alias record, or empty if none found. */ function drush_sitealias_get_record($alias, $alias_context = NULL) { // Check to see if the alias contains commas. If it does, then // we will go ahead and make a site list record $alias_record = array(); if (strpos($alias, ',') !== false) { // TODO: If the site list contains any site lists, or site // search paths, then we should expand those and merge them // into this list longhand. $alias_record['site-list'] = explode(',', $alias); } else { $alias_record = _drush_sitealias_get_record($alias, $alias_context); } if (!empty($alias_record)) { if (!array_key_exists('#name', $alias_record)) { $alias_record['#name'] = drush_sitealias_uri_to_site_dir($alias); } // Handle nested alias definitions and command-specific options. drush_set_config_special_contexts($alias_record); } return $alias_record; } /** * This is a continuation of drush_sitealias_get_record, above. It is * not intended to be called directly. */ function _drush_sitealias_get_record($alias, $alias_context = NULL) { $alias_record = array(); // Before we do anything else, load $alias if it needs to be loaded _drush_sitealias_load_alias($alias, $alias_context); // Check to see if the provided parameter is in fact a defined alias. $all_site_aliases =& drush_get_context('site-aliases'); if (array_key_exists($alias, $all_site_aliases)) { $alias_record = $all_site_aliases[$alias]; } // If the parameter is not an alias, then it is some form of // site specification (or it is nothing at all) else { if (isset($alias)) { // Cases 1.) - 4.): // We will check for a site specification if the alias has at least // two characters from the set '@', '/', '#'. if ((strpos($alias, '@') === FALSE ? 0 : 1) + ((strpos($alias, '/') === FALSE && strpos($alias, '\\') === FALSE) ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) { if ((substr($alias,0,7) != 'http://') && !drush_is_absolute_path($alias)) { // Add on a scheme so that "user:pass@server" will always parse correctly $parsed = parse_url('http://' . $alias); } else if (drush_is_windows() && drush_is_absolute_path($alias)) { // On windows if alias begins with a filesystem path we must add file:// scheme to make it parse correcly $parsed = parse_url('file:///' . $alias); } else { $parsed = parse_url($alias); } // Copy various parts of the parsed URL into the appropriate records of the alias record foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => 'root') as $url_key => $option_key) { if (array_key_exists($url_key, $parsed)) { _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]); } } // If the site specification has a query, also set the query items // in the alias record. This allows passing db_url as part of the // site specification, for example. if (array_key_exists('query', $parsed)) { foreach (explode('&', $parsed['query']) as $query_arg) { $query_components = explode('=', $query_arg); _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1])); } } // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host // Note: We presume that 'server' is the best default for case 3; without this code, the default would // be whatever is set in $options['l'] on the target machine's drushrc.php settings file. if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) { $alias_record['uri'] = $parsed['host']; } // Special checking: relative aliases embedded in a path $relative_alias_pos = strpos($alias_record['root'], '/@'); if ($relative_alias_pos !== FALSE) { // Special checking: /path/@sites $base = substr($alias_record['root'], 0, $relative_alias_pos); $relative_alias = substr($alias_record['root'], $relative_alias_pos + 1); if (drush_valid_drupal_root($base) || ($relative_alias == '@sites')) { drush_sitealias_create_sites_alias($base); $alias_record = drush_sitealias_get_record($relative_alias); } else { $alias_record = array(); } } } else { // Case 5.) and 6.): // If the alias is the name of a folder in the 'sites' directory, // then use it as a local site specification. $alias_record = _drush_sitealias_find_record_for_local_site($alias); } } } if (!empty($alias_record)) { if (!isset($alias_record['remote']) && !isset($alias_record['#loaded-config'])) { if (array_key_exists('root', $alias_record)) { drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush'); } $alias_site_dir = drush_sitealias_local_site_path($alias_record); if (isset($alias_site_dir)) { // Add the sites folder of this site to the alias search path list drush_sitealias_add_to_alias_path($alias_site_dir); // Load the drush config file if there is one associated with this alias if (!isset($alias_record['config'])) { $alias_record['config'] = realpath($alias_site_dir . '/drushrc.php'); } } if (isset($alias_record['config']) && file_exists($alias_record['config'])) { drush_load_config_file('site', $alias_record['config']); $alias_record['#loaded-config'] = TRUE; } unset($alias_record['config']); } // Add the static defaults _drush_sitealias_add_static_defaults($alias_record); // Cache the result with all of its calculated values $all_site_aliases[$alias] = $alias_record; } return $alias_record; } /** * Add a path to the array of paths where alias files are searched for. * * @param $add_path * A path to add to the search path (or NULL to not add any). * Once added, the new path will remain available until drush * exits. * @return * An array of paths containing all values added so far */ function drush_sitealias_add_to_alias_path($add_path) { static $site_paths = array(); if ($add_path != NULL) { if (!is_array($add_path)) { $add_path = explode(PATH_SEPARATOR, $add_path); } // Normalize path to make sure we don't add the same path twice on // windows due to different spelling. e.g. c:\tmp and c:/tmp foreach($add_path as &$path) { $path = drush_normalize_path($path); } $site_paths = array_unique(array_merge($site_paths, $add_path)); } return $site_paths; } /** * Return the array of paths where alias files are searched for. * * @param $alias_path_context * If the alias being looked up is part of a relative alias, * the alias path context specifies the context of the primary * alias the new alias is rooted from. Alias files stored in * the sites folder of this context, or inside the context itself * takes priority over any other search path that might define * a similarly-named alias. In this way, multiple sites can define * a '@peer' alias. * @return * An array of paths */ function drush_sitealias_alias_path($alias_path_context = NULL) { $context_path = array(); if (isset($alias_path_context)) { $context_path = array(drush_sitealias_local_site_path($alias_path_context)); } // We get the current list of site paths by adding NULL // (nothing) to the path list, which is a no-op $site_paths = drush_sitealias_add_to_alias_path(NULL); // If the user defined the root of a drupal site, then also // look for alias files there. $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); if (isset($drupal_root)) { $site_paths[] = drush_get_context('DRUSH_SELECTED_DRUPAL_SITES_ALL_DRUSH'); } $alias_path = (array) drush_get_context('ALIAS_PATH', array()); return array_unique(array_merge($context_path, $alias_path, $site_paths)); } /** * Return the full path to the site directory of the * given alias record. * * @param $alias_record * The alias record * @return * The path to the site directory of the associated * alias record, or NULL if the record is not a local site. */ function drush_sitealias_local_site_path($alias_record) { $result = NULL; if (isset($alias_record['uri']) && isset($alias_record['root']) && !isset($alias_record['remote-host'])) { $result = realpath($alias_record['root'] . '/sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri'])); } return $result; } /** * Check and see if an alias definition for $alias is available. * If it is, load it into the list of aliases cached in the * 'site-aliases' context. * * @param $alias * The name of the alias to load in ordinary form ('@name') * @param $alias_path_context * When looking up a relative alias, the alias path context is * the primary alias that we will start our search from. */ function _drush_sitealias_load_alias($alias, $alias_path_context = NULL) { $all_site_aliases = drush_get_context('site-aliases'); $result = array(); // Check to see if this is a relative alias ('@site/@peer') $relative_alias_pos = strpos($alias, '/@'); if ($relative_alias_pos !== false) { $primary_alias = substr($alias,0,$relative_alias_pos); $relative_alias = substr($alias,$relative_alias_pos + 1); $primary_record = drush_sitealias_get_record($primary_alias); _drush_sitealias_find_and_load_alias(substr($relative_alias,1), $primary_record); $result = drush_sitealias_get_record($relative_alias); if (!empty($result)) { if (array_key_exists('inherited', $result)) { $result = array_merge($primary_record, $result); } $result['#name'] = $relative_alias; _drush_sitealias_add_inherited_values_to_record($result); _drush_sitealias_cache_alias(substr($alias, 1), $result); } } else { // Only aliases--those named entities that begin with '@'--can be loaded this way. // We also skip any alias that has already been loaded. if ((substr($alias,0,1) == '@') && !array_key_exists($alias,$all_site_aliases)) { $aliasname = substr($alias,1); $result = _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context); if (!empty($result)) { $alias_options = array('site-aliases' => array($aliasname => $result)); _drush_sitealias_add_inherited_values($alias_options['site-aliases']); drush_set_config_special_contexts($alias_options); if (array_key_exists('#file', $result)) { drush_log(dt('Loaded alias !alias from file !file', array('!alias' => $alias, '!file' => $result['#file']))); } } } } return $result; } /** * Load every alias file that can be found anywhere in the * alias search path. */ function drush_sitealias_load_all($resolve_parent = TRUE) { $result = _drush_sitealias_find_and_load_all_aliases(); if (!empty($result) && ($resolve_parent == TRUE)) { // If any aliases were returned, then check for // inheritance and then store the aliases into the // alias cache _drush_sitealias_add_inherited_values($result); $alias_options = array('site-aliases' => $result); drush_set_config_special_contexts($alias_options); } } /** * Worker function called by _drush_sitealias_load_alias and * drush_sitealias_load_all. Traverses the alias search path * and finds the specified alias record. * * @return * An array of $kay => $value pair of alias names and alias records * loaded. */ function _drush_sitealias_find_and_load_all_aliases() { $result = array(); $drush_alias_files = _drush_sitealias_find_alias_files(); drush_set_context('drush-alias-files', $drush_alias_files); // For every file that matches, check inside it for // an alias with a matching name. foreach ($drush_alias_files as $filename) { if (file_exists($filename)) { $aliases = $options = array(); // silently ignore files we can't include if ((@include $filename) === FALSE) { drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), 'bootstrap'); continue; } unset($options['site-aliases']); // maybe unnecessary // If $aliases are not set, but $options are, then define one alias named // after the first word of the file, before '.alias.drushrc.php. if (empty($aliases) && !empty($options)) { $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.')); $aliases[$this_alias_name] = $options; $options = array(); } // If this is a group alias file, then make an // implicit alias from the group name that contains // a site-list of all of the aliases in the file $group_name = ''; if (substr($filename, -20) == ".aliases.drushrc.php") { $group_name = basename($filename,".aliases.drushrc.php"); if (!empty($aliases) && !array_key_exists($group_name, $aliases)) { $alias_names = array(); foreach (array_keys($aliases) as $one_alias) { $alias_names[] = "@$group_name.$one_alias"; $aliases["$group_name.$one_alias"] = $aliases[$one_alias]; unset($aliases[$one_alias]); } $aliases[$group_name] = array('site-list' => implode(',', $alias_names)); } } if (!empty($aliases)) { if (!empty($options)) { foreach ($aliases as $name => $value) { $aliases[$name] = array_merge($options, $value); } $options = array(); } foreach ($aliases as $name => $value) { _drush_sitealias_initialize_alias_record($aliases[$name]); } $result = _sitealias_array_merge($result, $aliases); // If we found at least one alias from this file // then record it in the drush-alias-files context. $drush_alias_files = drush_get_context('drush-alias-files'); if (!in_array($filename, $drush_alias_files)) { $drush_alias_files[] = $filename; } drush_set_context('drush-alias-files', $drush_alias_files); } } } return $result; } /** * Function to find all alias files that might contain aliases * that match the requested alias name. */ function _drush_sitealias_find_alias_files($aliasname = NULL, $alias_path_context = NULL) { $alias_files_to_consider = array(); // The alias path is a list of folders to search for alias settings files $alias_path = drush_sitealias_alias_path($alias_path_context); // $alias_files contains a list of filename patterns // to search for. We will find any matching file in // any folder in the alias path. The directory scan // is not deep, though; only files immediately in the // search path are considered. $alias_files = array('/.*aliases\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/'); if ($aliasname == NULL) { $alias_files[] = '/.*\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/'; } else { $alias_files[] = '/' . preg_quote($aliasname, '/') . '\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/'; } // Search each path in turn foreach ($alias_path as $path) { // Find all of the matching files in this location foreach ($alias_files as $file_pattern_to_search_for) { $alias_files_to_consider = array_merge($alias_files_to_consider, array_keys(drush_scan_directory($path, $file_pattern_to_search_for, drush_filename_blacklist(), 0, FALSE))); } } return $alias_files_to_consider; } /** * Traverses the alias search path and finds the specified alias record. * * @param $aliasname * The name of the alias without the leading '@' (i.e. '#name') * or NULL to load every alias found in every alias file. * @param $alias_path_context * When looking up a relative alias, the alias path context is * the primary alias that we will start our search from. * @return * An empty array if nothing was loaded. If $aliasname is * not null, then the array returned is the alias record for * $aliasname. If $aliasname is NULL, then the array returned * is a $kay => $value pair of alias names and alias records * loaded. */ function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = NULL) { $result = array(); $result_names = array(); // Special checking for '@sites' alias if ($aliasname == 'sites') { $drupal_root = NULL; if ($alias_path_context != null) { if (array_key_exists('root', $alias_path_context) && !array_key_exists('remote-host', $alias_path_context)) { $drupal_root = $alias_path_context['root']; } } else { $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); } if (isset($drupal_root) && !is_array($drupal_root)) { drush_sitealias_create_sites_alias($drupal_root); } } $alias_files_to_consider = _drush_sitealias_find_alias_files($aliasname, $alias_path_context); // For every file that matches, check inside it for // an alias with a matching name. $recorded_files = array(); foreach ($alias_files_to_consider as $filename) { if (file_exists($filename)) { $aliases = $options = array(); // silently ignore files we can't include if ((@include $filename) === FALSE) { drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), 'bootstrap'); continue; } unset($options['site-aliases']); // maybe unnecessary // If $aliases are not set, but $options are, then define one alias named // after the first word of the file, before '.alias.drushrc.php. if (empty($aliases) && !empty($options)) { $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.')); $aliases[$this_alias_name] = $options; $options = array(); } // If this is a group alias file, then make an // implicit alias from the group name that contains // a site-list of all of the aliases in the file $group_prefix = ''; if (substr($filename, -20) == ".aliases.drushrc.php") { $group_name = basename($filename,".aliases.drushrc.php"); $group_prefix = $group_name . '.'; if (!empty($aliases) && !array_key_exists($group_name, $aliases)) { $alias_names = array(); foreach (array_keys($aliases) as $one_alias) { $alias_names[] = "@$group_name.$one_alias"; $aliases[$one_alias]['#name'] = "$group_name.$one_alias"; $aliases[$one_alias]['#group'] = $group_name; $aliases["$group_name.$one_alias"] = $aliases[$one_alias]; $aliases[$one_alias]["#hidden"] = TRUE; } $aliases[$group_name] = array('site-list' => implode(',', $alias_names), '#group' => $group_name, '#name' => $group_name); } } // Store only the named alias into the alias cache if ((isset($aliases)) && !empty($aliasname) && array_key_exists($aliasname, $aliases)) { drush_set_config_special_contexts($options); // maybe unnecessary $one_result = array_merge($options, $aliases[$aliasname]); $one_result['#file'] = $filename; if (!array_key_exists('#name', $one_result)) { $one_result['#name'] = $aliasname; } _drush_sitealias_initialize_alias_record($one_result); // If the alias name is exactly the same as a previous match, then // merge the two records together if (!empty($result) && ($result['#name'] == $one_result['#name'])) { $result = _sitealias_array_merge($result, $one_result); } // Add the name of the found record to the list of results else { $result_names[] = "@" . $one_result['#name']; $result = $one_result; } } } } // If there are multiple matches, then return a list of results. if (count($result_names) > 1) { $result = array('site-list' => $result_names); } return $result; } /** * Merges two site aliases. * * array_merge_recursive is too much; we only want to run * array_merge on the common top-level keys of the array. * * @param array $site_alias_a * A site alias array. * @param array $site_alias_b * A site alias array. * @return * A site alias array where the keys from $site_alias_a are overwritten by the * keys from $site_alias_b. */ function _sitealias_array_merge($site_alias_a, $site_alias_b) { $result = $site_alias_a; foreach($site_alias_b as $key => $value) { if (is_array($value) && array_key_exists($key, $result)) { $result[$key] = array_merge($result[$key], $value); } else { $result[$key] = $value; } } return $result; } /** * Check to see if there is a 'parent' item in the alias; if there is, * then load the parent alias record and overlay the entries in the * current alias record on top of the items from the parent record. * * @param $aliases * An array of alias records that are modified in-place. */ function _drush_sitealias_add_inherited_values(&$aliases) { foreach ($aliases as $alias_name => $alias_value) { if (isset($alias_value['parent'])) { // Prevent circular references from causing an infinite loop _drush_sitealias_cache_alias($alias_name, array()); _drush_sitealias_add_inherited_values_to_record($alias_value); $aliases[$alias_name] = $alias_value; } } } function _drush_sitealias_add_inherited_values_to_record(&$alias_value) { if (isset($alias_value['parent'])) { // Fetch and merge in each parent foreach (explode(',', $alias_value['parent']) as $parent) { $parent_record = drush_sitealias_get_record($parent); unset($parent_record['#name']); unset($parent_record['#hidden']); $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases')); foreach ($array_based_keys as $array_based_key) { if (isset($alias_value[$array_based_key]) && isset($parent_record[$array_based_key])) { $alias_value[$array_based_key] = array_merge($parent_record[$array_based_key], $alias_value[$array_based_key]); } } $alias_value = array_merge($parent_record, $alias_value); } unset($alias_value['parent']); } } /** * Add an empty record for the specified alias name * * @param $alias_name * The name of the alias, without the leading "@" */ function _drush_sitealias_cache_alias($alias_name, $alias_record) { $cache =& drush_get_context('site-aliases'); // If the alias already exists in the cache, then merge // the new alias with the existing alias if (array_key_exists("@$alias_name", $cache)) { $alias_record = array_merge($cache["@$alias_name"], $alias_record); } $cache["@$alias_name"] = $alias_record; // If the alias record points at a local site, make sure // that both the sites/all/drush and the site folder for that site // are added to the alias path, so that other alias files // stored in those locations become searchable. if (!array_key_exists('remote-host', $alias_record) && !empty($alias_record['root'])) { drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush'); $site_dir = drush_sitealias_local_site_path($alias_record); if (isset($site_dir)) { drush_sitealias_add_to_alias_path($site_dir); } } } /** * If the alias record does not contain a 'databases' or 'db-url' * entry, then use backend invoke to look up the settings value * from the remote or local site. The 'db_url' form is preferred; * nothing is done if 'db_url' is not available (e.g. on a D7 site) * * @param $alias_record * The full alias record to populate with database settings */ function drush_sitealias_add_db_url(&$alias_record) { if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) { // We use sql-conf to fetch our database info. We set 'override-simulated' so that // we will fetch the database values even in --simulate mode. $values = drush_invoke_process($alias_record, "sql-conf", array(), array('db-url' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE)); if (isset($values['object']['db-url'])) { $alias_record['db-url'] = $values['object']['db-url']; } } } /** * Return the databases record from the alias record * * @param $alias_record * A record returned from drush_sitealias_get_record * @returns * A databases record (always in D7 format) or NULL * if the databases record could not be found. */ function sitealias_get_databases_from_record(&$alias_record) { $altered_record = drush_sitealias_add_db_settings($alias_record); return array_key_exists('databases', $alias_record) ? $alias_record['databases'] : NULL; } /** * Return the $db_spec record for the database associated with * the provided alias record. @see drush_sitealias_add_db_settings(), * which will be used to first add the database information to the * alias records, invoking sql-conf to look them up if necessary. * * The options 'database' and 'target' are used to specify which * specific database should be fetched from the database record; * they may appear in the alias definition, or may be taken from the * command line options. The values 'default' and 'default' are * used if these options are not specified in either location. * * Note that in the context of sql-sync, the site alias record will * be taken from one of the source or target aliases * (e.g. `drush sql-sync @source @target`), which will be overlayed with * any options that begin with 'source-' or 'target-', respectively. * Therefore, the commandline options 'source-database' and 'source-target' * (or 'target-database' and 'source-target') may also affect the operation * of this function. */ function drush_sitealias_get_db_spec(&$alias_record, $default_to_self = FALSE, $prefix = '') { $db_spec = NULL; $databases = sitealias_get_databases_from_record($alias_record); if (isset($databases) && !empty($databases)) { $database = drush_sitealias_get_option($alias_record, 'database', 'default', $prefix); $target = drush_sitealias_get_option($alias_record, 'target', 'default', $prefix); if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) { $db_spec = $databases[$database][$target]; } } elseif ($default_to_self) { $db_spec = _drush_sql_get_db_spec(); } if (isset($db_spec)) { $remote_host = drush_sitealias_get_option($alias_record, 'remote-host', NULL, $prefix); if (!drush_is_local_host($remote_host)) { $db_spec['remote-host'] = $remote_host; $db_spec['port'] = drush_sitealias_get_option($alias_record, 'remote-port', (isset($db_spec['port']) ? $db_spec['port'] : NULL), $prefix); } } return $db_spec; } /** * If the alias record does not contain a 'databases' or 'db-url' * entry, then use backend invoke to look up the settings value * from the remote or local site. The 'databases' form is * preferred; 'db_url' will be converted to 'databases' if necessary. * * @param $alias_record * The full alias record to populate with database settings */ function drush_sitealias_add_db_settings(&$alias_record) { $altered_record = FALSE; // If the alias record does not have a defined 'databases' entry, // then we'll need to look one up if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) { $values = drush_invoke_process($alias_record, "sql-conf", array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE)); if (is_array($values) && ($values['error_status'] == 0)) { $altered_record = TRUE; // If there are any special settings in the '@self' record returned by drush_invoke_process, // then add those into our altered record as well if (array_key_exists('self', $values)) { $alias_record = array_merge($values['self'], $alias_record); } drush_sitealias_cache_db_settings($alias_record, $values['object']); } } return $altered_record; } function drush_sitealias_cache_db_settings(&$alias_record, $databases) { if (!empty($databases)) { $alias_record['databases'] = $databases; } // If the name is set, then re-cache the record after we fetch the databases if (array_key_exists('#name', $alias_record)) { $all_site_aliases =& drush_get_context('site-aliases'); $all_site_aliases[$alias_record['#name']] = $alias_record; // Check and see if this record is a copy of 'self' if (($alias_record['#name'] != 'self') && array_key_exists('@self', $all_site_aliases) && array_key_exists('#name', $all_site_aliases['@self']) && ($all_site_aliases['@self']['#name'] == $alias_record['#name'])) { $all_site_aliases['@self'] = $alias_record; } } } /** * Check to see if we have already bootstrapped to a site. */ function drush_sitealias_is_bootstrapped_site($alias_record) { if (!isset($alias_record['remote-host']) && array_key_exists('root', $alias_record)) { $self_record = drush_sitealias_get_record("@self"); if (empty($self_record) || !array_key_exists('root', $self_record)) { // TODO: If we have not bootstrapped to a site yet, we could // perhaps bootstrap to $alias_record here. return FALSE; } elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) { return TRUE; } } return FALSE; } /** * Get the name of the current bootstrapped site */ function drush_sitealias_bootstrapped_site_name() { $site_name = NULL; $self_record = drush_sitealias_get_record('@self'); if (array_key_exists('#name', $self_record)) { $site_name = $self_record['#name']; } if (!isset($site_name) || ($site_name == '@self')) { $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); if (isset($drupal_root)) { $drupal_uri = drush_get_context('DRUSH_SELECTED_URI', 'default'); $drupal_uri = str_replace('http://', '', $drupal_uri); // TODO: Maybe use _drush_sitealias_find_local_alias_name? $site_name = $drupal_root . '#' . $drupal_uri; } } return $site_name; } /** * If there are any path aliases (items beginning with "%") in the test * string, then resolve them as path aliases and add them to the provided * alias record. * * @param $alias_record * The full alias record to use in path alias expansion * @param $test_string * A slash-separated list of path aliases to resolve * e.g. "%files/%special". */ function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') { $path_aliases = array_key_exists('path-aliases', $alias_record) ? $alias_record['path-aliases'] : array(); // Convert the test string into an array of items, and // from this make a comma-separated list of projects // that we can pass to 'drush status'. $test_array = explode('/', $test_string); $project_array = array(); foreach($test_array as $one_item) { if (!empty($one_item) && ($one_item[0] == '%') && (!array_key_exists($one_item,$path_aliases))) { $project_array[] = substr($one_item,1); } } $project_list = implode(',', $project_array); if (!empty($project_array)) { // Optimization: if we're already bootstrapped to the // site specified by $alias_record, then we can just // call _core_site_status_table() rather than use backend invoke. if (drush_sitealias_is_bootstrapped_site($alias_record) && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) { $status_values = _core_site_status_table($project_list); } else { $values = drush_invoke_process($alias_record, "status", array(), empty($project_list) ? array() : array('project' => $project_list), array('integrate' => FALSE, 'override-simulated' => TRUE)); $status_values = $values['object']; } if (isset($status_values['%paths'])) { foreach ($status_values['%paths'] as $key => $path) { $alias_record['path-aliases'][$key] = $path; } } } } /** * Given an alias record that is a site list (contains a 'site-list' entry), * resolve all of the members of the site list and return them * is an array of alias records. * * @param $alias_record * The site list alias record array * @return * An array of individual site alias records */ function drush_sitealias_resolve_sitelist($alias_record) { $result_list = array(); if (isset($alias_record)) { if (array_key_exists('site-list', $alias_record)) { foreach ($alias_record['site-list'] as $sitespec) { $one_result = drush_sitealias_get_record($sitespec); $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result)); } } elseif (array_key_exists('#name', $alias_record)) { $result_list[$alias_record['#name']] = $alias_record; } } return $result_list; } /** * Check to see if the uri is the same in the source and target * lists for all items in the array. This is a strong requirement * in D6; in D7, it is still highly convenient for the uri to * be the same, because the site folder name == the uri, and if * the uris match, then it is easier to rsync between remote machines. * * @param $source * Array of source alias records * @param $target * Array of target alias records to compare against source list * @return * TRUE iff the uris of the sources and targets are in alignment */ function drush_sitealias_check_lists_alignment($source, $target) { $is_aligned = TRUE; $i = 0; foreach ($source as $one_source) { if ((!isset($target[$i])) || (!_drush_sitelist_check_site_records($one_source, $target[$i]))) { $is_aligned = FALSE; break; } ++$i; } return $is_aligned; } /** * If the source and target lists contain alias records to the same * sets of sites, but in different orders, this routine will re-order * the lists so that they are in alignment. * * TODO: Review the advisability of this operation. */ function drush_sitelist_align_lists(&$source, &$target, &$source_result, &$target_result) { $source_result = array(); $target_result = array(); foreach ($source as $key => $one_source) { $one_target = _drush_sitelist_find_in_list($one_source, $target); if ($one_target !== FALSE) { $source_result[] = $one_source; $target_result[] = $one_target; unset($source[$key]); } } $source = $source_result; $target = $target_result; } function _drush_sitelist_find_in_list($one_source, &$target) { $result = FALSE; foreach ($target as $key => $one_target) { if(_drush_sitelist_check_site_records($one_source, $one_target)) { $result = $one_target; unset($target[$key]); } } return $result; } function _drush_sitelist_check_site_records($source, $target) { if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) { return TRUE; } return FALSE; } /** * Initialize an alias record; called as soon as the alias * record is loaded from its alias file, before it is stored * in the cache. * * @param alias_record * The alias record to be initialized; parameter is modified in place. */ function _drush_sitealias_initialize_alias_record(&$alias_record) { // If there is a 'from-list' entry, then build a derived // list based on the site list with the given name. if (array_key_exists('from-list', $alias_record)) { // danger of infinite loops... move to transient defaults? $from_record = drush_sitealias_get_record($alias_record['from-list']); $from_list = drush_sitealias_resolve_sitelist($from_record); $derived_list = array(); foreach ($from_list as $one_record) { $derived_record = _drush_sitealias_derive_record($one_record, $alias_record); $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record); } $alias_record = array(); if (!empty($derived_list)) { $alias_record['site-list'] = $derived_list; } } // If there is a 'site-search-path' entry, then build // a 'site-list' entry from all of the sites that can be // found in the search path. if (array_key_exists('site-search-path', $alias_record)) { // TODO: Is there any point in merging the sites from // the search path with any sites already listed in the // 'site-list' entry? For now we'll just overwrite. $search_path = $alias_record['site-search-path']; if (!is_array($search_path)) { $search_path = explode(',', $search_path); } $found_sites = _drush_sitealias_find_local_sites($search_path); $alias_record['site-list'] = $found_sites; // The 'unordered-list' flag indicates that the order of the items in the site list is not stable. $alias_record['unordered-list'] = '1'; // DEBUG: var_export($alias_record, FALSE); } if (array_key_exists('site-list', $alias_record)) { if (!is_array($alias_record['site-list'])) { $alias_record['site-list'] = explode(',', $alias_record['site-list']); } } } /** * Add "static" default values to the given alias record. The * difference between a static default and a transient default is * that static defaults -always- exist in the alias record, and * they are cached, whereas transient defaults are only added * if the given drush command explicitly adds them. * * @param alias_record * An alias record with most values already filled in */ function _drush_sitealias_add_static_defaults(&$alias_record) { // If there is a 'db-url' entry but not 'databases' entry, then we will // build 'databases' from 'db-url' so that drush commands that use aliases // can always count on using a uniform 'databases' array. if (isset($alias_record['db-url']) && !isset($alias_record['databases'])) { $alias_record['databases'] = drush_sitealias_convert_db_from_db_url($alias_record['db-url']); } // Adjustments for aliases to drupal instances (as opposed to aliases that are site lists) if (array_key_exists('uri', $alias_record)) { // Make sure that there is always a 'path-aliases' array if (!array_key_exists('path-aliases', $alias_record)) { $alias_record['path-aliases'] = array(); } // If there is a 'root' entry, then copy it to the '%root' path alias $alias_record['path-aliases']['%root'] = $alias_record['root']; } } function _drush_sitealias_derive_record($from_record, $modifying_record) { $result = $from_record; // If there is a 'remote-user' in the modifying record, copy it. if (array_key_exists('remote-user', $modifying_record)) { $result['remote-user'] = $from_record['remote_user']; } // If there is a 'remote-host', then: // If it is empty, clear the remote host in the result record // If it ends in '.', then prepend it to the remote host in the result record // Otherwise, copy it to the result record if (array_key_exists('remote-host', $modifying_record)) { $remote_host_modifier = $modifying_record['remote-host']; if(empty($remote_host_modifier)) { unset($result['remote-host']); unset($result['remote-user']); } elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') { $result['remote-host'] = $remote_host_modifier . $result['remote-host']; } else { $result['remote-host'] = $remote_host_modifier; } } // If there is a 'root', then: // If it begins with '/', copy it to the result record // Otherwise, append it to the result record if (array_key_exists('root', $modifying_record)) { $root_modifier = $modifying_record['root']; if($root_modifier[0] == '/') { $result['root'] = $root_modifier; } else { $result['root'] = $result['root'] . '/' . $root_modifier; } } // Poor man's realpath: take out the /../ with preg_replace. // (realpath fails if the files in the path do not exist) while(strpos($result['root'], '/../') !== FALSE) { $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']); } // TODO: Should we allow the uri to be transformed? // I think that if the uri does not match, then you should // always build the list by hand, and not rely on '_drush_sitealias_derive_record'. return $result; } /** * Convert from an alias record to a site specification * * @param alias_record * The full alias record to convert * * @param with_db * True if the site specification should include a ?db-url term * * @return string * The site specification */ function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) { $result = ''; // TODO: we should handle 'site-list' records too. if (array_key_exists('site-list', $alias_record)) { // TODO: we should actually expand the site list and recompose it $result = implode(',', $alias_record['site-list']); } else { // There should always be a uri if (array_key_exists('uri', $alias_record)) { $result = '#' . drush_sitealias_uri_to_site_dir($alias_record['uri']); } // There should always be a root if (array_key_exists('root', $alias_record)) { $result = $alias_record['root'] . $result; } if (array_key_exists('remote-host', $alias_record)) { $result = drush_remote_host($alias_record) . $result; } // Add the database info to the specification if desired if ($with_db) { // If db-url is not supplied, look it up from the remote // or local site and add it to the site alias if (!isset($alias_record['db-url'])) { drush_sitealias_add_db_url($alias_record); } $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']); } } return $result; } /** * Search for drupal installations in the search path. * * @param search_path * An array of drupal root folders * * @return * An array of site specifications (/path/to/root#sitename.com) */ function _drush_sitealias_find_local_sites($search_path) { $result = array(); foreach ($search_path as $a_drupal_root) { $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root)); } return $result; } /** * Return a list of all of the local sites at the specified drupal root. */ function _drush_find_local_sites_at_root($a_drupal_root = '', $search_depth = 1) { $site_list = array(); $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root ); if (!empty($base_path)) { if (drush_valid_drupal_root($base_path)) { // If $a_drupal_root is in fact a valid drupal root, then return // all of the sites found inside the 'sites' folder of this drupal instance. $site_list = _drush_find_local_sites_in_sites_folder($base_path); } else { $bootstrap_files = drush_scan_directory($base_path, '/' . basename(DRUSH_DRUPAL_SIGNATURE) . '/' , array('.', '..', 'CVS', 'examples'), 0, drush_get_option('search-depth', $search_depth) + 1, 'filename', 1); foreach ($bootstrap_files as $one_bootstrap => $info) { $includes_dir = dirname($one_bootstrap); if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_SIGNATURE))) { $drupal_root = dirname($includes_dir); $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list); } } } } return $site_list; } /** * Return a list of all of the local sites at the specified 'sites' folder. */ function _drush_find_local_sites_in_sites_folder($a_drupal_root) { $site_list = array(); // If anyone searches for sites at a given root, then // make sure that alias files stored at this root // directory are included in the alias search path drush_sitealias_add_to_alias_path($a_drupal_root); $base_path = $a_drupal_root . '/sites'; // TODO: build a cache keyed off of $base_path (realpath($base_path)?), // so that it is guarenteed that the lists returned will definitely be // exactly the same should this routine be called twice with the same path. $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1, 'filename', 1); foreach ($files as $filename => $info) { if ($info->basename == 'settings.php') { // First we'll resolve the realpath of the settings.php file, // so that we get the correct drupal root when symlinks are in use. $real_sitedir = dirname(realpath($filename)); $real_root = drush_locate_root($filename); if ($real_root !== FALSE) { $a_drupal_site = $real_root . '#' . basename($real_sitedir); } // If the symlink points to some folder outside of any drupal // root, then we'll use the else { $uri = drush_sitealias_site_dir_from_filename($filename); $a_drupal_site = $a_drupal_root . '#' . $uri; } // Add the site if it isn't already in the array if (!in_array($a_drupal_site, $site_list)) { $site_list[] = $a_drupal_site; } } } return $site_list; } function drush_sitealias_create_sites_alias($a_drupal_root = '') { $sites_list = _drush_find_local_sites_at_root($a_drupal_root); _drush_sitealias_cache_alias('sites', array('site-list' => $sites_list)); } /** * Add "transient" default values to the given alias record. The * difference between a static default and a transient default is * that static defaults -always- exist in the alias record, * whereas transient defaults are only added if the given drush * command explicitly calls this function. The other advantage * of transient defaults is that it is possible to differentiate * between a default value and an unspecified value, since the * transient defaults are not added until requested. * * Since transient defaults are not cached, you should avoid doing * expensive operations here. To be safe, drush commands should * avoid calling this function more than once. * * @param alias_record * An alias record with most values already filled in */ function _drush_sitealias_add_transient_defaults(&$alias_record) { if (isset($alias_record['path-aliases'])) { // Add the path to the drush folder to the path aliases as !drush if (!array_key_exists('%drush', $alias_record['path-aliases'])) { if (array_key_exists('%drush-script', $alias_record['path-aliases'])) { $alias_record['path-aliases']['%drush'] = dirname($alias_record['path-aliases']['%drush-script']); } else { $alias_record['path-aliases']['%drush'] = dirname($GLOBALS['argv'][0]); } } // Add the path to the site folder to the path aliases as !site if (!array_key_exists('%site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) { $alias_record['path-aliases']['%site'] = 'sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri']) . '/'; } } } /** * Find the name of a local alias record that has the specified * root and uri. */ function _drush_sitealias_find_local_alias_name($root, $uri) { $result = ''; $all_site_aliases =& drush_get_context('site-aliases'); foreach ($all_site_aliases as $alias_name => $alias_values) { if (!array_key_exists('remote-host', $alias_values) && array_key_exists('root', $alias_values) && array_key_exists('uri', $alias_values) && ($alias_name != '@self')) { if (($root == $alias_values['root']) && ($uri == $alias_values['uri'])) { $result = $alias_name; } } } return $result; } /** * If '$alias' is the name of a folder in the sites folder of the given drupal * root, then build an alias record for it * * @param alias * The name of the site in the 'sites' folder to convert * @return array * An alias record, or empty if none found. */ function _drush_sitealias_find_record_for_local_site($alias, $drupal_root = null) { $alias_record = array(); // Clip off the leading '#' if it is there if (substr($alias,0,1) == '#') { $alias = substr($alias,1); } if (!isset($drupal_root)) { $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT'); } if (isset($drupal_root)) { $alias_dir = drush_sitealias_uri_to_site_dir($alias); $site_settings_file = $drupal_root . '/sites/' . $alias_dir . '/settings.php'; $alias_record = drush_sitealias_build_record_from_settings_file($site_settings_file, $alias, $drupal_root); } return $alias_record; } function drush_sitealias_build_record_from_settings_file($site_settings_file, $alias = null, $drupal_root = null) { $alias_record = array(); if (file_exists($site_settings_file)) { if (!isset($drupal_root)) { $drupal_root = drush_locate_root($site_settings_file); } $alias_record['root'] = $drupal_root; if (isset($alias)) { $alias_record['uri'] = $alias; } else { $alias_record['uri'] = _drush_sitealias_site_dir_to_uri(drush_sitealias_site_dir_from_filename($site_settings_file)); } } return $alias_record; } /** * Pull the site directory from the path to settings.php * * @param site_settings_file * path to settings.php * * @return string * the site directory component of the path to settings.php */ function drush_sitealias_site_dir_from_filename($site_settings_file) { return basename(dirname($site_settings_file)); } /** * Convert from a URI to a site directory. * * @param uri * A uri, such as http://domain.com:8080/drupal * @return string * A directory, such as domain.com.8080.drupal */ function drush_sitealias_uri_to_site_dir($uri) { $uri = str_replace('http://', '', $uri); if (drush_is_windows()) { // Handle absolute paths on windows $uri = str_replace(array(':/', ':\\'), array('.', '.'), $uri); } return str_replace(array('/', ':', '\\'), array('.', '.', '.'), $uri); } /** * Convert from an old-style database URL to an array of database settings * * @param db_url * A Drupal 6 db-url string to convert, or an array with a 'default' element. * @return array * An array of database values containing only the 'default' element of * the db_url. */ function drush_convert_db_from_db_url($db_url) { if (is_array($db_url)) { $url = parse_url($db_url['default']); } else { $url = parse_url($db_url); } // Fill in defaults to prevent notices. $url += array( 'driver' => NULL, 'user' => NULL, 'pass' => NULL, 'host' => NULL, 'port' => NULL, 'path' => NULL, 'database' => NULL, ); $url = (object)array_map('urldecode', $url); return array( 'driver' => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme, 'username' => $url->user, 'password' => $url->pass, 'port' => $url->port, 'host' => $url->scheme == 'sqlite' ? '' : $url->host, // Remove leading / character from database names, unless we're installing // to SQLite (which won't have a slash there unless it's part of a path). 'database' => $url->scheme == 'sqlite' ? $url->host . $url->path : substr($url->path, 1), ); } /** * Convert from an old-style database URL to an array of database settings * * @param db_url * A Drupal 6 db-url string to convert, or an array with multiple db-urls. * @return array * An array of database values. */ function drush_sitealias_convert_db_from_db_url($db_url) { $result = array(); if (!is_array($db_url)) { $result = array('default' => array('default' => drush_convert_db_from_db_url($db_url))); } else { foreach ($db_url as $one_name => $one_db_url) { $result[$one_name] = array('default' => drush_convert_db_from_db_url($one_db_url)); } } return $result; } /** * Utility function used by drush_get_alias; keys that start with * '%' or '!' are path aliases, the rest are entries in the alias record. */ function _drush_sitealias_set_record_element(&$alias_record, $key, $value) { if ((substr($key,0,1) == '%') || (substr($key,0,1) == '!')) { $alias_record['path-aliases'][$key] = $value; } elseif (!empty($key)) { $alias_record[$key] = $value; } } /** * Looks up the specified alias record and calls through to * drush_sitealias_set_alias_context, below. * * @param alias * The name of the alias record * @param prefix * The prefix value to afix to the beginning of every * key set. * @return boolean * TRUE is an alias was found and processed. */ function _drush_sitealias_set_context_by_name($alias, $prefix = '') { $site_alias_settings = drush_sitealias_get_record($alias); if (!empty($site_alias_settings)) { // Create an alias '@self' _drush_sitealias_cache_alias('self', $site_alias_settings); drush_sitealias_set_alias_context($site_alias_settings, $prefix); // change the selected site to match the new --root and --uri, if any were set _drush_bootstrap_select_drupal_site(); return TRUE; } return FALSE; } /** * Given an alias record, overwrite its values with options * from the command line and other drush contexts as specified * by the provided prefix. For example, if the prefix is 'source-', * then any option 'source-foo' will set the value 'foo' in the * alias record. */ function drush_sitealias_overlay_options($site_alias_record, $prefix) { return array_merge($site_alias_record, drush_get_merged_prefixed_options($prefix)); } /** * First return an option set via drush_sitealias_overlay_options, if * any, then fall back on "%" . $option from the path aliases. */ function drush_sitealias_get_path_option($site_alias_record, $option, $default = NULL) { if (isset($site_alias_record) && array_key_exists($option, $site_alias_record)) { return $site_alias_record[$option]; } if (isset($site_alias_record) && array_key_exists('path-aliases', $site_alias_record) && array_key_exists("%$option", $site_alias_record['path-aliases'])) { return $site_alias_record['path-aliases']["%$option"]; } else { return $default; } } /** * Given a site alias record, copy selected fields from it * into the drush 'alias' context. The 'alias' context has * lower precedence than the 'cli' context, so values * set by an alias record can be overridden by command-line * parameters. * * @param site_alias_settings * An alias record * @param prefix * The prefix value to afix to the beginning of every * key set. For example, if this function is called once with * 'source-' and again with 'destination-' prefixes, then the * source database records will be stored in 'source-databases', * and the destination database records will be in * 'destination-databases'. */ function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') { $options = drush_get_context('alias'); // There are some items that we should just skip $skip_list = drush_get_special_keys(); // If 'php-options' are set in the alias, then we will force drush // to redispatch via the remote dispatch mechanism even if the target is localhost. if (array_key_exists('php-options', $site_alias_settings) || drush_get_context('DRUSH_BACKEND', FALSE)) { if (!array_key_exists('remote-host', $site_alias_settings)) { $site_alias_settings['remote-host'] = 'localhost'; } } // If 'php-options' are not set in the alias, then skip 'remote-host' // and 'remote-user' if 'remote-host' is actually the local machine. // This prevents drush from using the remote dispatch mechanism (the command // is just run directly on the local machine, bootstrapping to the specified alias) elseif (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) { $skip_list[] = 'remote-host'; $skip_list[] = 'remote-user'; } // If prefix is set, then copy from the 'prefix-' version // of the drush special keys ('command-specific', 'path-aliases') // into the ordinary version. This will allow us to set // 'source-command-specific' options that will only apply when // the alias is used as the source option for rsync or sql-sync. if (!empty($prefix)) { $special_contexts = drush_get_special_keys(); foreach ($special_contexts as $option_name) { if (array_key_exists($prefix . $option_name, $site_alias_settings)) { $site_alias_settings[$option_name] = array_key_exists($option_name, $site_alias_settings) ? array_merge($site_alias_settings[$option_name], $site_alias_settings[$prefix . $option_name]) : $site_alias_settings[$prefix . $option_name]; } } } // Transfer all options from the site alias to the drush options // in the 'alias' context. foreach ($site_alias_settings as $key => $value) { // Special handling for path aliases: if ($key == "path-aliases") { $path_aliases = $value; foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) { if (array_key_exists($path_key, $path_aliases)) { // Evaluate the path value, and substitute any path references found. // ex: '%dump-dir' => '%root/dumps' will store sql-dumps in the folder // 'dumps' in the Drupal root folder for the site. $evaluated_path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path_aliases[$path_key]); $options[$prefix . substr($path_key, 1)] = $evaluated_path; } } } // Special handling for command-specific elseif ($key == "command-specific") { $options[$key] = $value; } elseif (!in_array($key, $skip_list)) { $options[$prefix . $key] = $value; } } drush_set_config_options('alias', $options); } /** * Call prior to drush_sitealias_evaluate_path to insure * that any site-specific aliases associated with any * local site in $path are defined. */ function _drush_sitealias_preflight_path($path) { $alias = NULL; // Parse site aliases if there is a colon in the path // We allow: // @alias:/path // machine.domain.com:/path // machine:/path // Note that paths in the form "c:/path" are converted to // "/cygdrive/c/path" later; we do not want them to confuse // us here, so we skip paths that start with a single character // before the colon if we are running on Windows. Single-character // machine names are allowed in Linux only. $colon_pos = strpos($path, ':'); if ($colon_pos > (drush_is_windows("LOCAL") ? 1 : 0)) { $alias = substr($path, 0, $colon_pos); $path = substr($path, $colon_pos + 1); $site_alias_settings = drush_sitealias_get_record($alias); if (empty($site_alias_settings) && (substr($path,0,1) == '@')) { return NULL; } $machine = $alias; } else { $machine = ''; // if the path is a site alias or a local site... $site_alias_settings = drush_sitealias_get_record($path); if (empty($site_alias_settings) && (substr($path,0,1) == '@')) { return NULL; } if (!empty($site_alias_settings) || drush_is_local_host($path)) { $alias = $path; $path = ''; } } return array('alias' => $alias, 'path' => $path, 'machine' => $machine); } /** * Given a properly-escaped options string, replace any occurance of * %files and so on embedded inside it with its corresponding path. */ function drush_sitealias_evaluate_paths_in_options($option_string) { $path_aliases = _core_path_aliases(); return str_replace(array_keys($path_aliases), array_values($path_aliases), $option_string); } /** * Evaluate a path from its shorthand form to a literal path * usable by rsync. * * A path is "machine:/path" or "machine:path" or "/path" or "path". * 'machine' might instead be an alias record, or the name * of a site in the 'sites' folder. 'path' might be (or contain) * '%root' or some other path alias. This function will examine * all components of the path and evaluate them as necessary to * come to the final path. * * @param path * The path to evaluate * @param additional_options * An array of options that overrides whatever was passed in on * the command line (like the 'process' context, but only for * the scope of this one call). * @param local_only * If TRUE, force an error if the provided path points to a remote * machine. This is used in core-cli, for example, to insure that you * do not 'cd' to a remote machine. * @param os * This should be the local system os, unless evaluate path is * being called for rsync, in which case it should be "CWRSYNC" * if cwrsync is being used, or "rsync" to automatically select * between "LOCAL" and "CWRSYNC" based on the platform. * @return * The site record for the machine specified in the path, if any, * with the path to pass to rsync (including the machine specifier) * in the 'evaluated-path' item. */ function drush_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE, $os = NULL, $command_specific_prefix = '') { $site_alias_settings = array(); $path_aliases = array(); $remote_user = ''; $preflight = _drush_sitealias_preflight_path($path); if (!isset($preflight)) { return NULL; } $alias = $preflight['alias']; $path = $preflight['path']; $machine = $preflight['machine']; if (isset($alias)) { // Note that the alias settings may have an 'os' component, but we do // not want to use it here. The paths passed to rsync should always be // escaped per the LOCAL rules, without regard to the remote platform type. $site_alias_settings = drush_sitealias_get_record($alias); if (!empty($command_specific_prefix)) { drush_sitealias_command_default_options($site_alias_settings, $command_specific_prefix); } } if (!empty($site_alias_settings)) { if ($local_only && array_key_exists('remote-host', $site_alias_settings)) { return drush_set_error('DRUSH_REMOTE_SITE_IN_LOCAL_CONTEXT', dt("A remote site alias was used in a context where only a local alias is appropriate.")); } // Apply any options from this alias that might affect our rsync drush_sitealias_set_alias_context($site_alias_settings); // Use 'remote-host' from settings if available; otherwise site is local if (array_key_exists('remote-host', $site_alias_settings) && !drush_is_local_host($site_alias_settings['remote-host'])) { $machine = drush_remote_host($site_alias_settings); } else { $machine = ''; } } else { // Strip the machine portion of the path if the // alias points to the local machine. if (drush_is_local_host($machine)) { $machine = ''; } else { $machine = "$remote_user$machine"; } } // TOD: The code below is a little rube-goldberg-ish, and needs to be // reworked. core-rsync will call this function twice: once to // evaluate the destination, and then again to evaluate the source. Things // get odd with --exclude-paths, especially in conjunction with command-specific // and the --exclude-files option. @see testCommandSpecific() // If the --exclude-other-sites option is specified, then // convert that into --include-paths='%site' and --exclude-sites. if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_context('exclude-other-sites-processed', FALSE)) { $include_path_option = drush_get_option_override($additional_options, 'include-paths', ''); $additional_options['include-paths'] = '%site'; if (!empty($include_path_option)) { // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR. $additional_options['include-paths'] .= PATH_SEPARATOR . $include_path_option; } $additional_options['exclude-sites'] = TRUE; drush_set_context('exclude-other-sites-processed', TRUE); } else { unset($additional_options['include-paths']); } // If the --exclude-files option is specified, then // convert that into --exclude-paths='%files'. if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) { $exclude_path_option = drush_get_option_override($additional_options, 'exclude-paths', ''); $additional_options['exclude-paths'] = '%files'; if (!empty($exclude_path_option)) { // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR. $additional_options['exclude-paths'] .= PATH_SEPARATOR . $exclude_path_option; } $additional_options['exclude-files-processed'] = TRUE; } else { unset($additional_options['exclude-paths']); } // If there was no site specification given, and the // machine is local, then try to look // up an alias record for the default drush site. if (empty($site_alias_settings) && empty($machine)) { $drush_uri = drush_get_context('DRUSH_SELECTED_URI', 'default'); $site_alias_settings = drush_sitealias_get_record($drush_uri); } // Always add transient defaults _drush_sitealias_add_transient_defaults($site_alias_settings); // The $resolve_path variable is used by drush_sitealias_resolve_path_references // to test to see if there are any path references such as %site or %files // in it, so that resolution is only done if the path alias is referenced. // Therefore, we can concatenate without worrying too much about the structure of // this variable's contents. $include_path = drush_get_option_override($additional_options, 'include-paths', ''); $exclude_path = drush_get_option_override($additional_options, 'exclude-paths', ''); $resolve_path = $path . $include_path . $exclude_path; // Resolve path aliases such as %files, if any exist in the path if (!empty($resolve_path)) { drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path); } if (array_key_exists('path-aliases', $site_alias_settings)) { $path_aliases = $site_alias_settings['path-aliases']; } // Get the 'root' setting from the alias; if it does not // exist, then get the root from the bootstrapped site. if (array_key_exists('root', $site_alias_settings)) { $drupal_root = $site_alias_settings['root']; } else { drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE); $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); } if (empty($drupal_root)) { $drupal_root = ''; } else { // Add a slash to the end of the drupal root, as below. $drupal_root = drush_trim_path($drupal_root) . "/"; } $full_path_aliases = $path_aliases; foreach ($full_path_aliases as $key => $value) { // Expand all relative path aliases to be based off of the Drupal root if (!drush_is_absolute_path($value, "LOCAL") && ($key != '%root')) { $full_path_aliases[$key] = $drupal_root . $value; } // We do not want slashes on the end of our path aliases. $full_path_aliases[$key] = drush_trim_path($full_path_aliases[$key]); } // Fill in path aliases in the path, the include path and the exclude path. $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path); if (!empty($include_path)) { drush_set_option('include-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path)); } if (!empty($exclude_path)) { drush_set_option('exclude-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path)); } // Next make the rsync path, which includes the machine // and path components together. // First make empty paths or relative paths start from the drupal root. if (empty($path) || (!drush_is_absolute_path($path, "LOCAL"))) { $path = $drupal_root . $path; } // When calculating a path for use with rsync, we must correct // absolute paths in the form c:\path when cwrsync is in use. $path = drush_correct_absolute_path_for_exec($path, $os); // If there is a $machine component, to the path, then // add it to the beginning $evaluated_path = drush_escapeshellarg($path, $os); if (!empty($machine)) { $evaluated_path = $machine . ':' . $evaluated_path; } // // Add our result paths: // // evaluated-path: machine:/path // server-component: machine // path-component: :/path // path: /path // user-path: path (as specified in input parameter) // $site_alias_settings['evaluated-path'] = $evaluated_path; if (!empty($machine)) { $site_alias_settings['server-component'] = $machine; } $site_alias_settings['path-component'] = (!empty($path) ? ':' . $path : ''); $site_alias_settings['path'] = $path; $site_alias_settings['user-path'] = $preflight['path']; return $site_alias_settings; } /** * Option keys used for site selection. */ function drush_sitealias_site_selection_keys() { return array('remote-host', 'remote-user', 'winrs-password', 'ssh-options', '#name', 'os'); } function sitealias_find_local_drupal_root($site_list) { $drupal_root = NULL; foreach ($site_list as $site) { if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) { $drupal_root = $site['root']; } } return $drupal_root; } /** * Helper function to obtain the keys' names that need special handling in certain * cases. * @return * A non-associative array containing the needed keys' names. */ function drush_get_special_keys() { $special_keys = array( 'command-specific', 'site-aliases', ); return $special_keys; } /** * Read the tmp file where the persistent site setting is stored. * * @return string * A valid site specification. */ function drush_sitealias_site_get() { if (($filename = drush_sitealias_get_envar_filename()) && file_exists($filename)) { $site = file_get_contents($filename); return $site; } else { return FALSE; } } /** * Return the filename for the file tht stores the DRUPAL_SITE variable. * * @return string * Full path to tmp file. */ function drush_sitealias_get_envar_filename($filename_prefix = 'drush-drupal-site-') { $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp/'; if (function_exists('posix_getppid') && is_dir($tmp) && is_writable($tmp)) { $dir = $tmp . '/drush-env'; if (drush_mkdir($dir, FALSE)) { return $dir . '/' . $filename_prefix . posix_getppid(); } } return FALSE; } drush-5.10.0/lib/000077500000000000000000000000001222105546100134665ustar00rootroot00000000000000drush-5.10.0/lib/README.txt000066400000000000000000000003751222105546100151710ustar00rootroot00000000000000Drush adds external libraries here; examples include the Console Table and lightweight httpserver. This directory should be writable by the first user who runs Drush so that these dependencies can be downloaded. It may be set to read-only after that. drush-5.10.0/tests/000077500000000000000000000000001222105546100140625ustar00rootroot00000000000000drush-5.10.0/tests/COVERAGE.txt000066400000000000000000000043261222105546100160230ustar00rootroot00000000000000COMMANDS ------------ pm-download: GOOD. need test for `pm-download --select` (hard to test; depends on state of project releases on d.o.) pm-updatecode: GOOD. pm-update: GOOD. Implicitly tested by pm-updatecode and updatedb. pm-releasenotes pm-releases pm-enable: GOOD. testEnDisUnList(). pm-disable: GOOD. testEnDisUnList(). pm-uninstall: GOOD. testEnDisUnList(). pm-list: GOOD. testEnDisUnList(). pm-info: pm-refresh: version-control: FAIR. See updatecode. To be deprecated all git workflow after git.drupal.org? package-hander: sql-cli: sql-connect: sql-query: FAIR. Implicit by site-install sql-dump: FAIR. Implicitly tested by sqlSyncTest. sql-sync: GOOD. testLocalSqlSync(). need test: %dump, %dump-dir, --dump, --dump-dir, --source-dump, --target-dump, --source-dump-dir and --target-dump-dir and permutations of same used together. sql-drop: FAIR. Implicit by site-install sql-sanitize: updatedb: (Used to be implicitly tested siteUpgradeTest). archive-dump: GOOD archive-restore: GOOD. Has own test and implicitly tested by environment cache in Unish framework. help version: GOOD. Implicit by testStandaloneScript() php-eval: GOOD. Implicitly tested by many tests (e.g. completeTest). php-script: GOOD. drupal-directory: GOOD cache-get: GOOD cache-set: GOOD cache-clear: GOOD core-config: NONE core-cron core-status: FAIR: Implicit test by contextTest. docs core-rsync core-quick-drupal: GOOD image: GOOD queue-*: GOOD runserver search-* shellalias: GOOD need test: shell alias with site alias site-install: FAIR. Implicit test by setUpDrupal(). ssh: GOOD test-* topic usage-* variable-*: GOOD watchdog-*: GOOD user-*: GOOD. field-*: GOOD. make: GOOD INCLUDES ------------ backend: GOOD need test: --pipe with remote alias and --pipe with list of aliases batch: GOOD bootstrap: command: FAIR complete: GOOD context: FAIR. Many functions implicitly tested. Option merging (config, include, alias-path) not tested. drush: NONE. environment sitealias. FAIR. Explicit test for alias lists. Single aliases implicitly tested by contextTest. dbtng: Good. Implicit by variable-*. drupal exec: GOOD: Implicitly tested all over. filesystem output ROOT ------------- drush: need test: drush.ini drush.php drush.bat: N/A drush.complete.sh: N/A drush-5.10.0/tests/README.md000066400000000000000000000031771222105546100153510ustar00rootroot00000000000000Drush's test suite is based on [PHPUnit](http://www.phpunit.de). In order to maintain high quality, our tests are run on every push by [Travis](https://travis-ci.org/drush-ops/drush) Usage -------- 1. Install PHPUnit - Follow the [Composer installation instruction](http://getcomposer.org/download) - Run composer depending on your installation from the Drush root directory ```bash php composer.phar install --dev composer install --dev ``` 1. Review the configuration settings in phpunit.xml.dist. If customization is needed, copy to phpunit.xml and edit away. 1. Run unit tests: ```bash $ cd tests $ php ../vendor/phpunit/phpunit/phpunit.php . ``` Advanced usage --------- - Run only tests matching a regex: `phpunit --filter=testVersionString` - Skip slow tests (usually those with network usage): `phpunit --exclude-group slow` - XML results: `phpunit --filter=testVersionString --log-junit results.xml` Reuse by Drush Commandfiles ----------- Drush commandfiles are encouraged to ship with PHPUnit test cases that extend Drush_UnitTestCase and Drush_CommandTestCase. In order to run the tests, you have to point to the [drush_testcase.inc](tests/drush_testcase.inc) file such as `phpunit --bootstrap=/path/to/drush/tests/drush_testcase.inc`. The devel project does exactly this - http://drupalcode.org/project/devel.git/blob/refs/heads/8.x-1.x:/develDrushTest.php Cache ----------- In order to speed up test runs, Unish (the drush testing class) caches built Drupal sites and restores them as requested by tests. Once in while, you might need to clear this cache by deleting the /drush-cache directory. drush-5.10.0/tests/archiveDumpTest.php000066400000000000000000000100451222105546100177020ustar00rootroot00000000000000fetchInstallDrupal(self::uri, TRUE, UNISH_DRUPAL_MAJOR_VERSION, 'testing'); $root = $this->webroot(); $dump_dest = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'dump.tar.gz'; $options = array( 'root' => $root, 'uri' => self::uri, 'yes' => NULL, 'destination' => $dump_dest, 'overwrite' => NULL, ); if ($no_core) { $options['no-core'] = NULL; } $this->drush('archive-dump', array(self::uri), $options); return $dump_dest; } /** * Untar an archive and return the path to the untarred folder. */ private function unTar($dump_dest) { $untar_dest = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'untar'; unish_file_delete_recursive($untar_dest); $tar = self::get_tar_executable(); $exec = sprintf("mkdir %s && cd %s && $tar -xzf %s", $untar_dest, $untar_dest, $dump_dest); $this->execute($exec); return $untar_dest; } /** * Test if tarball generated by archive-dump looks right. */ public function testArchiveDump() { $dump_dest = $this->archiveDump(FALSE); $docroot = basename($this->webroot()); // Check the dump file is a gzip file. $exec = sprintf('file %s', $dump_dest); $this->execute($exec); $output = $this->getOutput(); $sep = self::is_windows() ? ';' : ':'; $expected = $dump_dest . "$sep gzip compressed data, from"; $this->assertStringStartsWith($expected, $output); // Untar the archive and make sure it looks right. $untar_dest = $this->unTar($dump_dest); if (strpos(UNISH_DB_URL, 'mysql') !== FALSE) { $this->execute(sprintf('head %s/unish_%s.sql | grep "MySQL dump"', $untar_dest, self::uri)); } $this->assertFileExists($untar_dest . '/MANIFEST.ini'); $this->assertFileExists($untar_dest . '/' . $docroot); return $dump_dest; } /** * Test archive-restore. * * Restore the archive generated in testArchiveDump() and verify that the * directory contents are identical. * * @depends testArchiveDump */ public function testArchiveRestore($dump_dest) { require_once dirname(__FILE__) . '/../includes/filesystem.inc'; $restore_dest = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'restore'; $options = array( 'yes' => NULL, 'destination' => $restore_dest, ); $this->drush('archive-restore', array($dump_dest), $options); $original_codebase = drush_dir_md5($this->webroot()); $restored_codebase = drush_dir_md5($restore_dest); $this->assertEquals($original_codebase, $restored_codebase); } /** * Test if tarball generated by archive-dump with --no-core looks right. */ public function testArchiveDumpNoCore() { $dump_dest = $this->archiveDump(TRUE); $untar_dest = $this->unTar($dump_dest); $docroot = basename($this->webroot()); $this->assertFileExists($untar_dest . '/MANIFEST.ini'); $this->assertFileExists($untar_dest . '/' . $docroot); $this->assertFileNotExists($untar_dest . '/' . $docroot . '/modules', 'No modules directory should exist with --no-core'); return $dump_dest; } /** * Test archive-restore for a site archive (--no-core). * * @depends testArchiveDumpNoCore */ public function testArchiveRestoreNoCore($dump_dest) { $root = $this->webroot(); $original_codebase = drush_dir_md5($root); unish_file_delete_recursive($root . '/sites/' . self::uri); $options = array( 'yes' => NULL, 'destination' => $root, ); $this->drush('archive-restore', array($dump_dest), $options); $restored_codebase = drush_dir_md5($root); $this->assertEquals($original_codebase, $restored_codebase); } } drush-5.10.0/tests/backendTest.php000066400000000000000000000311141222105546100170220ustar00rootroot00000000000000 '/fake/path/to/root', 'uri' => 'default'); EOD; file_put_contents($aliasFile, $aliasContents); $options = array( 'alias-path' => $aliasPath, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. 'backend' => TRUE, ); $php = << TRUE)); \$valuesWithoutAlias = drush_invoke_process("@dev", "unit-return-argv", array(), array(), array()); return array('with' => \$valuesUsingAlias['object'], 'without' => \$valuesWithoutAlias['object']); EOD; $this->drush('php-eval', array($php), $options); $parsed = parse_backend_output($this->getOutput()); // $parsed['with'] and $parsed['without'] now contain an array // each with the original arguments passed in with and without // 'dispatch-using-alias', respectively. $argDifference = array_diff($parsed['object']['with'], $parsed['object']['without']); $this->assertEquals(array_diff(array_values($argDifference), array('@foo.dev')), array()); $argDifference = array_diff($parsed['object']['without'], $parsed['object']['with']); $this->assertEquals(array_diff(array_values($argDifference), array('--root=/fake/path/to/root', '--uri=default')), array()); } /* * Covers the following origin responsibilities. * - A remote host is recognized in site specification. * - Generates expected ssh command. * * General handling of site aliases will be in sitealiasTest.php. */ function testOrigin() { $exec = sprintf('%s %s version arg1 arg2 --simulate --ssh-options=%s | grep ssh', UNISH_DRUSH, self::escapeshellarg('user@server/path/to/drupal#sitename'), self::escapeshellarg('-i mysite_dsa')); $this->execute($exec); $bash = $this->escapeshellarg('drush --invoke --simulate --uri=sitename --root=/path/to/drupal version arg1 arg2 2>&1'); $expected = "Simulating backend invoke: ssh -i mysite_dsa user@server $bash 2>&1"; $output = $this->getOutput(); $this->assertEquals($expected, $output, 'Expected ssh command was built'); } /* * Covers the following target responsibilities. * - Interpret stdin as options as per REST API. * - Successfully execute specified command. * - JSON object has expected contents (including errors). * - JSON object is wrapped in expected delimiters. */ function testTarget() { $stdin = json_encode(array('filter'=>'sql')); $exec = sprintf('echo %s | %s version --backend', self::escapeshellarg($stdin), UNISH_DRUSH); $this->execute($exec); $parsed = parse_backend_output($this->getOutput()); $this->assertTrue((bool) $parsed, 'Successfully parsed backend output'); $this->assertArrayHasKey('log', $parsed); $this->assertArrayHasKey('output', $parsed); $this->assertArrayHasKey('object', $parsed); $this->assertEquals(self::EXIT_SUCCESS, $parsed['error_status']); // This assertion shows that `help` was called and that stdin options were respected. $this->assertStringStartsWith('drush ', $parsed['output']); $this->assertEquals('Bootstrap to phase 0.', $parsed['log'][0]['message']); // Check error propogation by requesting an invalid command (missing Drupal site). $this->drush('core-cron', array(), array('backend' => NULL), NULL, NULL, self::EXIT_ERROR); $parsed = parse_backend_output($this->getOutput()); $this->assertEquals(1, $parsed['error_status']); $this->assertArrayHasKey('DRUSH_NO_DRUPAL_ROOT', $parsed['error_log']); } /* * Covers the following target responsibilities. * - Insures that the 'Drush version' line from drush status appears in the output. * - Insures that the backend output start marker appears in the output (this is a backend command). * - Insures that the drush output appears before the backend output start marker (output is displayed in 'real time' as it is produced). */ function testRealtimeOutput() { $exec = sprintf('%s core-status --backend --nocolor 2>&1', UNISH_DRUSH); $this->execute($exec); $output = $this->getOutput(); $drush_version_offset = strpos($output, "Drush version"); $backend_output_offset = strpos($output, "DRUSH_BACKEND_OUTPUT_START>>>"); $this->assertTrue($drush_version_offset !== FALSE, "'Drush version' string appears in output."); $this->assertTrue($backend_output_offset !== FALSE, "Drush backend output marker appears in output."); $this->assertTrue($drush_version_offset < $backend_output_offset, "Drush version string appears in output before the backend output marker."); } /** * Covers the following target responsibilities. * - Insures that function result is returned in --backend mode */ function testBackendFunctionResult() { $php = "return 'bar'"; $this->drush('php-eval', array($php), array('backend' => NULL)); $parsed = parse_backend_output($this->getOutput()); // assert that $parsed has 'bar' $this->assertEquals("'bar'", var_export($parsed['object'], TRUE)); } /** * Covers the following target responsibilities. * - Insures that backend_set_result is returned in --backend mode * - Insures that the result code for the function does not overwrite * the explicitly-set value */ function testBackendSetResult() { $php = "drush_backend_set_result('foo'); return 'bar'"; $this->drush('php-eval', array($php), array('backend' => NULL)); $parsed = parse_backend_output($this->getOutput()); // assert that $parsed has 'foo' and not 'bar' $this->assertEquals("'foo'", var_export($parsed['object'], TRUE)); } /** * Covers the following target responsibilities. * - Insures that the backend option 'invoke-multiple' will cause multiple commands to be executed. * - Insures that the right number of commands run. * - Insures that the 'concurrent'-format result array is returned. * - Insures that correct results are returned from each command. */ function testBackendInvokeMultiple() { $options = array( 'backend' => NULL, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. ); $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y'), array('invoke-multiple' => '3')); return \$values;"; $this->drush('php-eval', array($php), $options); $parsed = parse_backend_output($this->getOutput()); // assert that $parsed has a 'concurrent'-format output result $this->assertEquals('concurrent', implode(',', array_keys($parsed['object']))); // assert that the concurrent output has indexes 0, 1 and 2 (in any order) $concurrent_indexes = array_keys($parsed['object']['concurrent']); sort($concurrent_indexes); $this->assertEquals('0,1,2', implode(',', $concurrent_indexes)); foreach ($parsed['object']['concurrent'] as $index => $values) { // assert that each result contains 'x' => 'y' and nothing else $this->assertEquals("array ( 'x' => 'y', )", var_export($values['object'], TRUE)); } } /** * Covers the following target responsibilities. * - Insures that arrays are stripped when using --backend mode's method GET * - Insures that arrays can be returned as the function result of * backend invoke. */ function testBackendMethodGet() { $options = array( 'backend' => NULL, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. ); $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'data' => array('a' => 1, 'b' => 2)), array('method' => 'GET')); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';"; $this->drush('php-eval', array($php), $options); $parsed = parse_backend_output($this->getOutput()); // assert that $parsed has 'x' but not 'data' $this->assertEquals("array ( 'x' => 'y', )", var_export($parsed['object'], TRUE)); } /** * Covers the following target responsibilities. * - Insures that complex arrays can be passed through when using --backend mode's method POST * - Insures that arrays can be returned as the function result of * backend invoke. */ function testBackendMethodPost() { $options = array( 'backend' => NULL, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. ); $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'data' => array('a' => 1, 'b' => 2)), array('method' => 'POST')); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';"; $this->drush('php-eval', array($php), $options); $parsed = parse_backend_output($this->getOutput()); // assert that $parsed has 'x' and 'data' $this->assertEquals(array ( 'x' => 'y', 'data' => array ( 'a' => 1, 'b' => 2, ), ), $parsed['object']); } /** * Covers the following target responsibilities. * - Insures that backend invoke can properly re-assemble packets * that are split across process-read-size boundaries. * * This test works by repeating testBackendMethodGet(), while setting * '#process-read-size' to a very small value, insuring that packets * will be split. */ function testBackendReassembleSplitPackets() { $options = array( 'backend' => NULL, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. ); $read_sizes_to_test = array(4096, 128, 16); foreach ($read_sizes_to_test as $read_size) { $log_message=""; for ($i = 1; $i <= 16; $i++) { $log_message .= "X"; $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('log-message' => '$log_message', 'x' => 'y$read_size', 'data' => array('a' => 1, 'b' => 2)), array('method' => 'GET', '#process-read-size' => $read_size)); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';"; $this->drush('php-eval', array($php), $options); $parsed = parse_backend_output($this->getOutput()); // assert that $parsed has 'x' but not 'data' $all_warnings=array(); foreach ($parsed['log'] as $log) { if ($log['type'] == 'warning') { $all_warnings[] = $log['message']; } } $this->assertEquals("$log_message,done", implode(',', $all_warnings), 'Log reconstruction with read_size ' . $read_size); $this->assertEquals("array ( 'x' => 'y$read_size', )", var_export($parsed['object'], TRUE)); } } } } class backendUnitCase extends Drush_UnitTestCase { /** * Covers the following target responsibilities. * - Insures that drush_invoke_process called with fork backend set is able * to invoke a non-blocking process. */ function testBackendFork() { // Need to set DRUSH_COMMAND so that drush will be called and not phpunit define('DRUSH_COMMAND', UNISH_DRUSH); // Ensure that file that will be created by forked process does not exist // before invocation. $test_file = UNISH_SANDBOX . '/fork_test.txt'; if (file_exists($test_file)) { unlink($test_file); } // Sleep for a milisecond, then create the file $ev_php = "usleep(1000);fopen('$test_file','a');"; drush_invoke_process("@none", "ev", array($ev_php), array(), array("fork" => TRUE)); // Test file does not exist immediate after process forked $this->assertEquals(file_exists($test_file), FALSE); // Check every 100th of a second for up to 4 seconds to see if the file appeared $repetitions = 400; while (!file_exists($test_file) && ($repetitions > 0)) { usleep(10000); } // Assert that the file did finally appear $this->assertEquals(file_exists($test_file), TRUE); } } drush-5.10.0/tests/batchTest.php000066400000000000000000000017011222105546100165130ustar00rootroot00000000000000setUpDrupal(1, TRUE); $site = reset($sites); $root = $this->webroot(); $uri = key($sites); $name = "example"; $options = array( 'root' => $root, 'uri' => $uri, 'yes' => NULL, 'include' => dirname(__FILE__), ); $this->drush('unit-batch', array(), $options); // Collect log messages that begin with "!!!" (@see: _drush_unit_batch_operation()) $parsed = parse_backend_output($this->getOutput()); $special_log_msgs = ''; foreach ($parsed['log'] as $key => $log) { if(substr($log['message'],0,3) == '!!!') { $special_log_msgs .= $log['message']; } } $this->assertEquals("!!! ArrayObject does its job.", $special_log_msgs, 'Batch messages were logged'); } } drush-5.10.0/tests/cacheCommandTest.php000066400000000000000000000026011222105546100177740ustar00rootroot00000000000000setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'root' => $this->webroot(), 'uri' => key($this->sites), ); // Test the cache get command. $this->drush('cache-get', array('schema'), $options + array('format' => 'json')); $schema = json_decode($this->getOutput()); $this->assertObjectHasAttribute('data', $schema); // Test that get-ing a non-existant cid fails. $this->drush('cache-get', array('test-failure-cid'), $options + array('format' => 'json')); $output = json_decode($this->getOutput()); $this->assertEmpty($output); // Test setting a new cache item. $cache_test_value = 'cache test string'; $this->drush('cache-set', array('cache-test-cid', $cache_test_value), $options); $this->drush('cache-get', array('cache-test-cid'), $options + array('format' => 'json')); $cache_value = json_decode($this->getOutput()); $this->assertEquals($cache_test_value, $cache_value->data); // Test cache-clear all. $this->drush('cache-clear', array('all'), $options); $this->drush('cache-get', array('cache-test-cid'), $options + array('format' => 'json')); $output = json_decode($this->getOutput()); $this->assertEmpty($output); } } drush-5.10.0/tests/commandSpecificTest.php000066400000000000000000000064111222105546100205210ustar00rootroot00000000000000 UNISH_SANDBOX, 'uri' => 'site1.com', 'source-command-specific' => array( 'core-rsync' => array( 'exclude-paths' => 'excluded_by_source', ), ), 'target-command-specific' => array( 'core-rsync' => array( 'exclude-paths' => 'excluded_by_target', ), ), 'path-aliases' => array( '%files' => 'sites/default/files', ), ); $contents = $this->file_aliases($aliases); $return = file_put_contents($path, $contents); } function testCommandSpecific() { $options = array( 'alias-path' => UNISH_SANDBOX, 'simulate' => NULL, 'include-vcs' => NULL, ); $this->drush('core-rsync', array('/tmp', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = trim($this->getOutput()); $this->assertContains('excluded_by_target', $output); $this->drush('core-rsync', array('@site1', '/tmp'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = trim($this->getOutput()); $this->assertContains('excluded_by_source', $output); $this->drush('core-rsync', array('@site1', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = trim($this->getOutput()); $this->assertContains('excluded_by_target', $output); // Now do that all again with 'exclude-files' $options['exclude-files'] = NULL; $this->drush('core-rsync', array('/tmp', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = trim($this->getOutput()); $this->assertContains('sites/default/files', $output); $this->assertContains('excluded_by_target', $output); $this->assertNotContains('include-vcs', $output); $this->assertNotContains('exclude-paths', $output); $this->assertNotContains('exclude-files-processed', $output); $this->drush('core-rsync', array('@site1', '/tmp'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = trim($this->getOutput()); $this->assertContains('sites/default/files', $output); // This one does not work. @see drush_sitealias_evaluate_path // $this->assertContains('excluded_by_source', $output); $this->assertNotContains('include-vcs', $output); $this->assertNotContains('exclude-paths', $output); $this->assertNotContains('exclude-files-processed', $output); $this->drush('core-rsync', array('@site1', '@site1'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = trim($this->getOutput()); $this->assertContains('sites/default/files', $output); $this->assertContains('excluded_by_target', $output); $this->assertNotContains('include-vcs', $output); $this->assertNotContains('exclude-paths', $output); $this->assertNotContains('exclude-files-processed', $output); } } drush-5.10.0/tests/commandTest.php000066400000000000000000000067071222105546100170630ustar00rootroot00000000000000 dirname(__FILE__), ); $this->drush('unit-invoke', array(), $options, NULL, NULL, self::EXIT_ERROR); $called = json_decode($this->getOutput()); $this->assertSame($expected, $called); } /* * Assert that minimum bootstrap phase is honored. * * Not testing dependency on a module since that requires an installed Drupal. * Too slow for little benefit. */ public function testRequirementBootstrapPhase() { // Assure that core-cron fails when run outside of a Drupal site. $return = $this->drush('core-cron', array(), array('quiet' => NULL), NULL, NULL, self::EXIT_ERROR); } /** * Assert that unknown options are caught and flagged as errors */ public function testUnknownOptions() { // Make sure an ordinary 'version' command works $return = $this->drush('version', array(), array('pipe' => NULL)); // Add an unknown option --magic=1234 and insure it fails $return = $this->drush('version', array(), array('pipe' => NULL, 'magic' => 1234), NULL, NULL, self::EXIT_ERROR); // Finally, add in a hook that uses drush_hook_help_alter to allow the 'magic' option. // We need to run 'drush cc drush' to clear the commandfile cache; otherwise, our include will not be found. $include_path = dirname(__FILE__) . '/hooks/magic_help_alter'; $this->drush('version', array(), array('include' => $include_path, 'pipe' => NULL, 'magic' => '1234', 'strict' => NULL)); } /** * Assert that errors are thrown for commands with missing callbacks. */ public function testMissingCommandCallback() { $options = array( 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. //'show-invoke' => TRUE, ); $this->drush('missing-callback', array(), $options, NULL, NULL, self::EXIT_ERROR); } /** * Assert that commands depending on unknown commandfiles are detected. */ public function testMissingDrushDependency() { $options = array( 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. 'backend' => NULL, // To obtain and parse the error log. ); $this->drush('unit-drush-dependency', array(), $options, NULL, NULL, self::EXIT_ERROR); $parsed = parse_backend_output($this->getOutput()); $this->assertArrayHasKey("DRUSH_COMMANDFILE_DEPENDENCY_ERROR", $parsed['error_log']); } /** * Assert that commands in disabled modules are detected. */ public function testDisabledModule() { $sites = $this->setUpDrupal(1, TRUE); $uri = key($sites); $root = $this->webroot(); $options = array( 'root' => $root, 'uri' => $uri, ); $this->drush('pm-download', array('devel'), $options); $options += array( 'backend' => NULL, // To obtain and parse the error log. ); $this->drush('devel-download', array(), $options, NULL, NULL, self::EXIT_ERROR); $parsed = parse_backend_output($this->getOutput()); $this->assertArrayHasKey("DRUSH_COMMAND_DEPENDENCY_ERROR", $parsed['error_log']); } } drush-5.10.0/tests/commandUnitTest.php000066400000000000000000000047011222105546100177130ustar00rootroot00000000000000drush_major_version(); $major_plus1 = $major + 1; // Write matched and unmatched files to the system search path. $files = array( $path . "/$major.drush$major.inc", $path . "/drush$major/drush$major.drush.inc", $path . "/$major_plus1.drush$major_plus1.inc", $path . "/drush$major_plus1/drush$major_plus1.drush.inc", ); mkdir($path); mkdir($path . '/drush' . $major); mkdir($path . '/drush' . $major_plus1); foreach ($files as $file) { $contents = <<assertTrue(in_array($files[0], $loaded), 'Loaded a version-specific command file.'); $this->assertTrue(in_array($files[1], $loaded), 'Loaded a version-specific command directory.'); $this->assertFalse(in_array($files[2], $loaded), 'Did not load a mismatched version-specific command file.'); $this->assertFalse(in_array($files[3], $loaded), 'Did not load a a mismatched version-specific command directory.'); } /* * Assert that $command has interesting properties. Reference command by * it's alias (dl) to assure that those aliases are built as expected. */ public function testGetCommands() { drush_bootstrap(DRUSH_BOOTSTRAP_DRUSH); $commands = drush_get_commands(); $command = $commands['dl']; $this->assertEquals('dl', current($command['aliases'])); $this->assertArrayHasKey('version_control', $command['engines']); $this->assertArrayHasKey('package_handler', $command['engines']); $this->assertArrayHasKey('release_info', $command['engines']); $this->assertEquals('pm-download', $command['command']); $this->assertEquals('pm', $command['commandfile']); $this->assertEquals('drush_command', $command['callback']); $this->assertArrayHasKey('examples', $command['sections']); $this->assertTrue($command['is_alias']); $command = $commands['pm-download']; $this->assertArrayNotHasKey('is_alias', $command); } } drush-5.10.0/tests/completeTest.php000066400000000000000000000171751222105546100172560ustar00rootroot00000000000000 'pm-uninstall', ); "; file_put_contents(UNISH_SANDBOX . '/drushrc.php', trim($contents)); } public function testComplete() { // We copy our completetest commandfile into our path. // We cannot use --include since complete deliberately avoids drush // command dispatch. copy(dirname(__FILE__) . '/completetest.drush.inc', getenv('HOME') . '/.drush/completetest.drush.inc'); $sites = $this->setUpDrupal(2); $env = key($sites); $root = $this->webroot(); // We copy the unit test command into (only) our dev site, so we have a // detectable difference we can use to detect cache correctness between // sites. mkdir("$root/sites/$env/modules"); copy(dirname(__FILE__) . '/completetestsite.drush.inc', "$root/sites/$env/modules/completetestsite.drush.inc"); // Clear the cache, so it finds our test command. $this->drush('php-eval', array('drush_cache_clear_all();'), array(), '@' . $env); // Create a sample directory and file to test file/directory completion. mkdir("aardvark"); touch('aard wolf.tar.gz'); // Create directory for temporary debug logs. mkdir(UNISH_SANDBOX . '/complete-debug'); // Test cache clearing for global cache, which should affect all // environments. First clear the cache: $this->drush('php-eval', array('drush_complete_cache_clear();')); // Confirm we get cache rebuilds for runs both in and out of a site // which is expected since these should resolve to separate cache IDs. $this->verifyComplete('@dev aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-zebra', FALSE); $this->verifyComplete('aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-wolf', FALSE); // Next, rerun and check results to confirm cache IDs are generated // correctly on our fast bootstrap when returning the cached result. $this->verifyComplete('@dev aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-zebra'); $this->verifyComplete('aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-wolf'); // Test cache clearing for a completion type, which should be effective only // for current environment - i.e. a specific site should not be effected. $this->drush('php-eval', array('drush_complete_cache_clear("command-names");')); $this->verifyComplete('@dev aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-zebra'); $this->verifyComplete('aaaaaaaard-', 'aaaaaaaard-ant', 'aaaaaaaard-wolf', FALSE); // Test cache clearing for a command specific completion type, which should // be effective only for current environment. Prime caches first. $this->verifyComplete('@dev aaaaaaaard a', 'aardvark', 'aardwolf', FALSE); $this->verifyComplete('aaaaaaaard a', 'aardvark', 'aardwolf', FALSE); $this->drush('php-eval', array('drush_complete_cache_clear("arguments", "aaaaaaaard");')); // We cleared the global cache for this argument, not the site specific // cache should still exist. $this->verifyComplete('@dev aaaaaaaard a', 'aardvark', 'aardwolf'); $this->verifyComplete('aaaaaaaard a', 'aardvark', 'aardwolf', FALSE); // Test overall context sensitivity - almost all of these are cache hits. // No context (i.e. "drush "), should list aliases and commands. $this->verifyComplete('""', '@dev', 'zzzzzzzzebra'); // Site alias alone. $this->verifyComplete('@', '@dev', '@stage'); // Command alone. $this->verifyComplete('aaaaaaaa', 'aaaaaaaard', 'aaaaaaaard-wolf'); // Command with single result. $this->verifyComplete('aaaaaaaard-v', 'aaaaaaaard-vark', 'aaaaaaaard-vark'); // Command with no results should produce no output. $this->verifyComplete('dont-name-a-command-like-this', '', ''); // Commands that start the same as another command (i.e. aaaaaaaard is a // valid command, but we should still list aaaaaaaardwolf when completing on // "aaaaaaaard"). $this->verifyComplete('@dev aaaaaaaard', 'aaaaaaaard', 'aaaaaaaard-zebra'); // Global option alone. $this->verifyComplete('--n', '--no', '--nocolor'); // Site alias + command. $this->verifyComplete('@dev aaaaaaaa', 'aaaaaaaard', 'aaaaaaaard-zebra'); // Site alias + command, should allow no further site aliases or commands. $this->verifyComplete('@dev aaaaaaaard-wolf @', '', '', FALSE); $this->verifyComplete('@dev aaaaaaaard-wolf aaaaaaaa', '', ''); // Command + command option. $this->verifyComplete('aaaaaaaard --', '--ears', '--nose'); // Site alias + command + command option. $this->verifyComplete('@dev aaaaaaaard --', '--ears', '--nose'); // Command + all arguments $this->verifyComplete('aaaaaaaard ""', 'aardvark', 'zebra'); // Command + argument. $this->verifyComplete('aaaaaaaard a', 'aardvark', 'aardwolf'); // Site alias + command + regular argument. // Note: this is checked implicitly by the argument cache testing above. if ($this->is_windows()) { $this->markTestSkipped('Complete tests not fully working nor needed on Windows.'); } // Site alias + command + file/directory argument. This is a command // argument we have not used so far, so a cache miss is expected. $this->verifyComplete('archive-restore aard', 'aard wolf.tar.gz', 'aardvark/', FALSE); // Site alias + command + file/directory argument with quoting. $this->verifyComplete('archive-restore aard\ w', 'aard\ wolf.tar.gz', 'aard\ wolf.tar.gz'); } /** * Helper function to call completion and make common checks. * * @param $command * The command line to attempt to complete. * @param $first * String indicating the expected first completion suggestion. * @param $last * String indicating the expected last completion suggestion. * @param bool $cache_hit * Optional parameter, if TRUE or omitted the debug log is checked to * ensure a cache hit on the last cache debug log entry, if FALSE then a * cache miss is checked for. */ function verifyComplete($command, $first, $last, $cache_hit = TRUE) { // We capture debug output to a separate file, so we can check for cache // hits/misses. $debug_file = tempnam(UNISH_SANDBOX . '/complete-debug', 'complete-debug'); // Commands should take the format: // drush --early=includes/complete.inc [--complete-debug] drush [@alias] [command]... $exec = sprintf('%s --early=includes/complete.inc --config=%s --complete-debug %s %s 2> %s', UNISH_DRUSH, UNISH_SANDBOX . '/drushrc.php', UNISH_DRUSH, $command, $debug_file); $this->execute($exec); $result = $this->getOutputAsList(); $actual = reset($result); $this->assertEquals("$command: (f) $first", "$command: (f) $actual"); $actual = end($result); $this->assertEquals("$command: (l) $last", "$command: (l) $actual"); // If checking for HIT, we ensure no MISS exists, if checking for MISS we // ensure no HIT exists. However, we exclude the first cache report, since // it is expected that the command-names cache (loaded when matching // command names) may sometimes be a HIT even when we are testing for a MISS // in the actual cache we are loading to complete against. $check_not_exist = 'HIT'; if ($cache_hit) { $check_not_exist = 'MISS'; } $contents = file_get_contents($debug_file); $first_cache_pos = strpos($contents, 'Cache ') + 6; $this->assertFalse(strpos($contents, 'Cache ' . $check_not_exist . ' cid', $first_cache_pos)); unlink($debug_file); } } drush-5.10.0/tests/completetest.drush.inc000066400000000000000000000022171222105546100204130ustar00rootroot00000000000000 'No-op command, used to test various completions for commands that start the same as other commands.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, 'callback' => 'drush_completetest_noop', ); } $items['aaaaaaaard']['arguments'] = array('name' => 'Name'); $items['aaaaaaaard']['options'] = array( 'ears' => 'Ears', 'eyes' => 'Eyes', 'nose' => 'Nose', 'legs' => 'Legs', ); return $items; } function drush_completetest_noop() { // No-op. } /** * Command argument complete callback. * * @return * Array of completions. */ function completetest_completetest_noop_complete() { return array( 'values' => array( 'aardvark', 'aardwolf', 'zebra', ), ); } drush-5.10.0/tests/completetestsite.drush.inc000066400000000000000000000007701222105546100213020ustar00rootroot00000000000000 'No-op command, used to test various completions for commands that start the same as other commands.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, 'callback' => 'drush_completetest_noop', ); return $items; } drush-5.10.0/tests/contextTest.php000066400000000000000000000146211222105546100171230ustar00rootroot00000000000000log("webroot: ".$this->webroot()."\n"); $this->env = key($this->sites); $this->site = $this->webroot() . '/sites/' . $this->env; $this->home = UNISH_SANDBOX . '/home'; $this->paths = array( 'custom' => UNISH_SANDBOX, 'site' => $this->site, 'drupal' => $this->webroot() . '/sites/all/drush', 'user' => $this->home, 'home.drush' => $this->home . '/.drush', 'system' => UNISH_SANDBOX . '/etc/drush', // We don't want to write a file into drush dir since it is not in the sandbox. // 'drush' => dirname(realpath(UNISH_DRUSH)), ); // Run each path through realpath() since the paths we'll compare against // will have already run through drush_load_config_file(). foreach ($this->paths as $key => $path) $this->paths[$key] = realpath($path); } /** * Try to write a tiny drushrc.php to each place that drush checks. Also * write a sites/dev/aliases.drushrc.php file to the sandbox. */ function setUp() { parent::setUp(); $this->setUpDrupal(); $this->setUpPaths(); // These files are only written to sandbox so get automatically cleaned up. foreach ($this->paths as $key => $path) { $contents = <<written[] = $this->convert_path($path); } } // Also write a site alias so we can test its supremacy in context hierarchy. $path = $this->site . '/aliases.drushrc.php'; $aliases['contextAlias'] = array( 'contextConfig' => 'alias1', 'command-specific' => array ( 'unit-eval' => array ( 'contextConfig' => 'alias-specific', ), ), ); $contents = $this->file_aliases($aliases); $return = file_put_contents($path, $contents); } /** * These should be different tests but I could not work out how to do that * without calling setUp() twice. setUpBeforeClass() did not work out (for MW). */ function testContext() { $this->ConfigSearchPaths(); $this->ConfigVersionSpecific(); $this->ContextHierarchy(); } /** * Assure that all possible config files get loaded. */ function ConfigSearchPaths() { $options = array( 'pipe' => NULL, 'config' => UNISH_SANDBOX, 'root' => $this->webroot(), 'uri' => $this->env ); $this->drush('core-status', array('Drush configuration'), $options); $output = trim($this->getOutput()); $loaded = explode(' ', $output); $loaded = array_map(array(&$this, 'convert_path'), $loaded); $this->assertSame($this->written, $loaded); } /** * Assure that matching version-specific config files are loaded and others are ignored. */ function ConfigVersionSpecific() { $major = $this->drush_major_version(); // Arbitrarily choose the system search path. $path = realpath(UNISH_SANDBOX . '/etc/drush'); $contents = <<drush('core-status', array('Drush configuration'), array('pipe' => NULL)); $output = trim($this->getOutput()); $loaded = explode(' ', $output); // Next 2 lines needed for Windows compatibility. $loaded = array_map(array(&$this, 'convert_path'), $loaded); $files = array_map(array(&$this, 'convert_path'), $files); $this->assertTrue(in_array($files[0], $loaded), 'Loaded a version-specific config file.'); $this->assertFalse(in_array($files[1], $loaded), 'Did not load a mismatched version-specific config file.'); } /** * Assure that options are loaded into right context and hierarchy is * respected by drush_get_option(). * * Stdin context not exercised here. See backendCase::testTarget(). */ function ContextHierarchy() { // The 'custom' config file has higher priority than cli and regular config files. $eval = '$contextConfig = drush_get_option("contextConfig", "n/a");'; $eval .= '$cli1 = drush_get_option("cli1");'; $eval .= 'print json_encode(get_defined_vars());'; $config = UNISH_SANDBOX . '/drushrc.php'; $options = array( 'cli1' => NULL, 'config' => $config, 'root' => $this->webroot(), 'uri' => $this->env, ); $this->drush('php-eval', array($eval), $options); $output = $this->getOutput(); $actuals = json_decode(trim($output)); $this->assertEquals('custom', $actuals->contextConfig); $this->assertTrue($actuals->cli1); // Site alias trumps 'custom'. $eval = '$contextConfig = drush_get_option("contextConfig", "n/a");'; $eval .= 'print json_encode(get_defined_vars());'; $options = array( 'config' => $config, 'root' => $this->webroot(), 'uri' => $this->env, ); $this->drush('php-eval', array($eval), $options, '@contextAlias'); $output = $this->getOutput(); $actuals = json_decode(trim($output)); $this->assertEquals('alias1', $actuals->contextConfig); // Command specific wins over non-specific. If it did not, $expected would // be 'site'. Note we call unit-eval command in order not to purturb // php-eval with options in config file. $eval = '$contextConfig = drush_get_option("contextConfig", "n/a");'; $eval .= 'print json_encode(get_defined_vars());'; $options = array( 'root' => $this->webroot(), 'uri' => $this->env, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. ); $this->drush('unit-eval', array($eval), $options); $output = $this->getOutput(); $actuals = json_decode(trim($output)); $this->assertEquals('site-specific', $actuals->contextConfig); } } drush-5.10.0/tests/coreTest.php000066400000000000000000000117371222105546100163740ustar00rootroot00000000000000setUpDrupal(1, TRUE); $root = $this->webroot(); $site = key($this->sites); $options = array( 'root' => $root, 'uri' => key($this->sites), 'simulate' => NULL, 'include-conf' => NULL, 'include-vcs' => NULL, 'yes' => NULL, 'invoke' => NULL, // invoke from script: do not verify options ); $this->drush('core-rsync', array("@$site:%files", "/tmp"), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1;'); $output = $this->getOutput(); $level = $this->log_level(); $pattern = in_array($level, array('verbose', 'debug')) ? "Calling system(rsync -e 'ssh ' -akzv --stats --progress --yes --invoke %s /tmp);" : "Calling system(rsync -e 'ssh ' -akz --yes --invoke %s /tmp);"; $expected = sprintf($pattern, UNISH_SANDBOX . "/web/sites/$site/files"); $this->assertEquals($expected, $output); } /** * Test to see if the optimized code path in drush_sitealias_resolve_path_references * that avoids a call to backend invoke when evaluating %files works. */ function testPercentFilesOptimization() { $this->setUpDrupal(1, TRUE); $root = $this->webroot(); $site = key($this->sites); $options = array( 'root' => $root, 'uri' => key($this->sites), 'simulate' => NULL, 'include-conf' => NULL, 'include-vcs' => NULL, 'yes' => NULL, 'invoke' => NULL, // invoke from script: do not verify options ); $php = '$a=drush_sitealias_get_record("@' . $site . '"); drush_sitealias_resolve_path_references($a, "%files"); print_r($a["path-aliases"]["%files"]);'; $this->drush('ev', array($php), $options); $output = $this->getOutput(); $expected = "sites/dev/files"; $this->assertEquals($expected, $output); } /* * Test standalone php-script scripts. Assure that script args and options work. */ public function testStandaloneScript() { if ($this->is_windows()) { $this->markTestSkipped('Standalone scripts not currently available on Windows.'); } $this->drush('version', array('drush_version'), array('pipe' => NULL)); $standard = $this->getOutput(); // Write out a hellounish.script into the sandbox. The correct /path/to/drush // is in the shebang line. $filename = 'hellounish.script'; $data = '#!/usr/bin/env [PATH-TO-DRUSH] $arg = drush_shift(); drush_invoke("version", $arg); '; $data = str_replace('[PATH-TO-DRUSH]', UNISH_DRUSH, $data); $script = UNISH_SANDBOX . '/' . $filename; file_put_contents($script, $data); chmod($script, 0755); $this->execute("$script drush_version --pipe"); $standalone = $this->getOutput(); $this->assertEquals($standard, $standalone); } function testDrupalDirectory() { $this->setUpDrupal(1, TRUE); $root = $this->webroot(); $options = array( 'root' => $root, 'uri' => key($this->sites), 'verbose' => NULL, 'skip' => NULL, // No FirePHP 'yes' => NULL, 'cache' => NULL, 'invoke' => NULL, // invoke from script: do not verify options ); $this->drush('pm-download', array('devel'), $options); $this->drush('pm-enable', array('devel', 'menu'), $options); $this->drush('drupal-directory', array('devel'), $options); $output = $this->getOutput(); $this->assertEquals($root . '/sites/all/modules/devel', $output); $this->drush('drupal-directory', array('%files'), $options); $output = $this->getOutput(); $this->assertEquals($root . '/sites/dev/files', $output); $this->drush('drupal-directory', array('%modules'), $options); $output = $this->getOutput(); $this->assertEquals($root . '/sites/all/modules', $output); } function testCoreRequirements() { $this->setUpDrupal(1, TRUE); $root = $this->webroot(); $options = array( 'root' => $root, 'uri' => key($this->sites), 'pipe' => NULL, 'ignore' => 'cron,http requests,update_core', // no network access when running in tests, so ignore these 'invoke' => NULL, // invoke from script: do not verify options ); // Verify that there are no severity 2 items in the status report $this->drush('core-requirements', array(), $options + array('severity' => '2')); $output = $this->getOutput(); $this->assertEquals('', $output); $this->drush('core-requirements', array(), $options); $output = $this->getOutput(); $expected="database_system: -1 database_system_version: -1 drupal: -1 file system: -1 install_profile: -1 node_access: -1 php: -1 php_extensions: -1 php_memory_limit: -1 php_register_globals: -1 settings.php: -1 unicode: 0 update: 0 update access: -1 update status: -1 webserver: -1"; $this->assertEquals($expected, trim($output)); } } drush-5.10.0/tests/devel.xml000066400000000000000000000767441222105546100157250ustar00rootroot00000000000000 Devel devel moshe weitzman 6.x 2 1,2 2 published http://drupal.org/project/devel ProjectsModules ProjectsAdministration ProjectsDeveloper ProjectsUtility ProjectsDrush Maintenance statusActively maintained devel 6.x-2.2 6.x-2.2 DRUPAL-6--2-2 2 2 unpublished http://drupal.org/node/990464 http://ftp.drupal.org/files/projects/devel-6.x-2.2.tar.gz 1291650063 07251c1cd56daf7d402fb5aea7dcfe48 172845 http://ftp.drupal.org/files/projects/devel-6.x-2.2.tar.gz tar.gz 07251c1cd56daf7d402fb5aea7dcfe48 172845 1291650063 http://ftp.drupal.org/files/projects/devel-6.x-2.2.zip zip 086412aa4c02ced793455486b48261e9 219993 1293230730 Release typeNew features Release typeBug fixes devel 6.x-2.2-rc1 6.x-2.2-rc1 DRUPAL-6--2-2-RC1 2 rc1 2 published http://drupal.org/node/990464 http://ftp.drupal.org/files/projects/devel-6.x-2.2-rc1.tar.gz 1291650063 07251c1cd56daf7d402fb5aea7dcfe48 172845 http://ftp.drupal.org/files/projects/devel-6.x-2.2-rc1.tar.gz tar.gz 07251c1cd56daf7d402fb5aea7dcfe48 172845 1291650063 http://ftp.drupal.org/files/projects/devel-6.x-2.2-rc1.zip zip 086412aa4c02ced793455486b48261e9 219993 1293230730 Release typeNew features Release typeBug fixes devel 6.x-2.1 6.x-2.1 DRUPAL-6--2-1 2 1 published http://drupal.org/node/990464 http://ftp.drupal.org/files/projects/devel-6.x-2.1.tar.gz 1291650063 07251c1cd56daf7d402fb5aea7dcfe48 172845 http://ftp.drupal.org/files/projects/devel-6.x-2.1.tar.gz tar.gz 07251c1cd56daf7d402fb5aea7dcfe48 172845 1291650063 http://ftp.drupal.org/files/projects/devel-6.x-2.1.zip zip 086412aa4c02ced793455486b48261e9 219993 1293230730 Release typeNew features Release typeBug fixes devel 6.x-1.23 6.x-1.23 DRUPAL-6--1-23 1 23 published http://drupal.org/node/990464 http://ftp.drupal.org/files/projects/devel-6.x-1.23.tar.gz 1291650063 07251c1cd56daf7d402fb5aea7dcfe48 172845 http://ftp.drupal.org/files/projects/devel-6.x-1.23.tar.gz tar.gz 07251c1cd56daf7d402fb5aea7dcfe48 172845 1291650063 http://ftp.drupal.org/files/projects/devel-6.x-1.23.zip zip 086412aa4c02ced793455486b48261e9 219993 1293230730 Release typeNew features Release typeBug fixes devel 6.x-1.22 6.x-1.22 DRUPAL-6--1-22 1 22 published http://drupal.org/node/882460 http://ftp.drupal.org/files/projects/devel-6.x-1.22.tar.gz 1281718291 a8a87cae8f101c4aad46688524ab7583 175140 http://ftp.drupal.org/files/projects/devel-6.x-1.22.tar.gz tar.gz a8a87cae8f101c4aad46688524ab7583 175140 1281718291 http://ftp.drupal.org/files/projects/devel-6.x-1.22.zip zip cc207ab34421975216e6ee424c595001 222352 1293230727 Release typeBug fixes devel 6.x-1.21 6.x-1.21 DRUPAL-6--1-21 1 21 published http://drupal.org/node/874116 http://ftp.drupal.org/files/projects/devel-6.x-1.21.tar.gz 1280961079 15bf95b8c3fbe3f570f3f7b590f52ded 175008 http://ftp.drupal.org/files/projects/devel-6.x-1.21.tar.gz tar.gz 15bf95b8c3fbe3f570f3f7b590f52ded 175008 1280961079 http://ftp.drupal.org/files/projects/devel-6.x-1.21.zip zip f19fe1188a2774b383f146aee228a083 222217 1293230734 Release typeSecurity update devel 6.x-1.20 6.x-1.20 DRUPAL-6--1-20 1 20 published http://drupal.org/node/777998 http://ftp.drupal.org/files/projects/devel-6.x-1.20.tar.gz 1271886306 b605d6a42e430660ef3418541d711c1c 174042 http://ftp.drupal.org/files/projects/devel-6.x-1.20.tar.gz tar.gz b605d6a42e430660ef3418541d711c1c 174042 1271886306 http://ftp.drupal.org/files/projects/devel-6.x-1.20.zip zip ff11ac877bdbbb29fa9dba024871e3bc 221132 1293230727 Release typeBug fixes devel 6.x-1.19 6.x-1.19 DRUPAL-6--1-19 1 19 published http://drupal.org/node/746888 http://ftp.drupal.org/files/projects/devel-6.x-1.19.tar.gz 1268976904 375c3b79e9e74fb636bf8a6d2fde87d6 173570 http://ftp.drupal.org/files/projects/devel-6.x-1.19.tar.gz tar.gz 375c3b79e9e74fb636bf8a6d2fde87d6 173570 1268976904 http://ftp.drupal.org/files/projects/devel-6.x-1.19.zip zip b9da9992305f2aa8e26bd9550c119189 220677 1293230724 Release typeNew features Release typeBug fixes devel 6.x-1.18 6.x-1.18 DRUPAL-6--1-18 1 18 published http://drupal.org/node/585982 http://ftp.drupal.org/files/projects/devel-6.x-1.18.tar.gz 1253731829 5f2b9e5f4b74beec35a1d1cff379ab5c 165785 http://ftp.drupal.org/files/projects/devel-6.x-1.18.tar.gz tar.gz 5f2b9e5f4b74beec35a1d1cff379ab5c 165785 1253731829 http://ftp.drupal.org/files/projects/devel-6.x-1.18.zip zip 2e2808ec65aaa44d23f2a96c6af8bfbb 216330 1293230728 Release typeBug fixes Release typeSecurity update devel 6.x-1.17 6.x-1.17 DRUPAL-6--1-17 1 17 published http://drupal.org/node/554032 http://ftp.drupal.org/files/projects/devel-6.x-1.17.tar.gz 1250713853 31b2cbea82226a729b11bbc91920a7cd 163024 http://ftp.drupal.org/files/projects/devel-6.x-1.17.tar.gz tar.gz 31b2cbea82226a729b11bbc91920a7cd 163024 1250713853 http://ftp.drupal.org/files/projects/devel-6.x-1.17.zip zip 5e2a367f2e43c90de1e676e3037e1570 213291 1293230731 Release typeNew features Release typeBug fixes devel 6.x-1.16 6.x-1.16 DRUPAL-6--1-16 1 16 published http://drupal.org/node/430072 http://ftp.drupal.org/files/projects/devel-6.x-1.16.tar.gz 1239375932 6dc83de5de101460f8eee2db3ea4b7d9 163998 http://ftp.drupal.org/files/projects/devel-6.x-1.16.tar.gz tar.gz 6dc83de5de101460f8eee2db3ea4b7d9 163998 1239375932 http://ftp.drupal.org/files/projects/devel-6.x-1.16.zip zip 65ad91bc0308b10a9d11107f2d35bc2f 215213 1293230728 Release typeBug fixes devel 6.x-1.15 6.x-1.15 DRUPAL-6--1-15 1 15 published http://drupal.org/node/419014 http://ftp.drupal.org/files/projects/devel-6.x-1.15.tar.gz 1238471715 e7cd175671ed62a95bab7093c636e663 163945 http://ftp.drupal.org/files/projects/devel-6.x-1.15.tar.gz tar.gz e7cd175671ed62a95bab7093c636e663 163945 1238471715 http://ftp.drupal.org/files/projects/devel-6.x-1.15.zip zip f0719bb64bdeb1d3c2f327457351b9d1 215342 1293230731 Release typeNew features Release typeBug fixes devel 6.x-1.14 6.x-1.14 DRUPAL-6--1-14 1 14 published http://drupal.org/node/368174 http://ftp.drupal.org/files/projects/devel-6.x-1.14.tar.gz 1233610814 11af2d7fa3050febfe3def770683f173 162780 http://ftp.drupal.org/files/projects/devel-6.x-1.14.tar.gz tar.gz 11af2d7fa3050febfe3def770683f173 162780 1233610814 http://ftp.drupal.org/files/projects/devel-6.x-1.14.zip zip c691c6d03d1f605493807cf7dad7dce1 214014 1293230725 Release typeBug fixes devel 6.x-1.13 6.x-1.13 DRUPAL-6--1-13 1 13 published http://drupal.org/node/353382 http://ftp.drupal.org/files/projects/devel-6.x-1.13.tar.gz 1230872109 669870433908fccd31e7de9b57434c64 161893 http://ftp.drupal.org/files/projects/devel-6.x-1.13.tar.gz tar.gz 669870433908fccd31e7de9b57434c64 161893 1230872109 http://ftp.drupal.org/files/projects/devel-6.x-1.13.zip zip 06ed93306fe8476af6aa62aac763afa4 212645 1293230728 Release typeNew features Release typeBug fixes devel 6.x-1.12 6.x-1.12 DRUPAL-6--1-12 1 12 published http://drupal.org/node/319216 http://ftp.drupal.org/files/projects/devel-6.x-1.12.tar.gz 1223583012 69b345c09ece34812b04e7ccbc2cdf23 146188 http://ftp.drupal.org/files/projects/devel-6.x-1.12.tar.gz tar.gz 69b345c09ece34812b04e7ccbc2cdf23 146188 1223583012 http://ftp.drupal.org/files/projects/devel-6.x-1.12.zip zip 8c66e91fc8c64388facf8e7e7054e58a 197116 1293230726 Release typeNew features Release typeBug fixes devel 6.x-1.11 6.x-1.11 DRUPAL-6--1-11 1 11 published http://drupal.org/node/310335 http://ftp.drupal.org/files/projects/devel-6.x-1.11.tar.gz 1221759046 93bc2cc8da26d759e46b24f1b9d4e3e2 145693 http://ftp.drupal.org/files/projects/devel-6.x-1.11.tar.gz tar.gz 93bc2cc8da26d759e46b24f1b9d4e3e2 145693 1221759046 http://ftp.drupal.org/files/projects/devel-6.x-1.11.zip zip 3c1063443d81927e3b90e129bc169a24 196477 1293230729 Release typeNew features Release typeBug fixes devel 6.x-1.10 6.x-1.10 DRUPAL-6--1-10 1 10 published http://drupal.org/node/283183 http://ftp.drupal.org/files/projects/devel-6.x-1.10.tar.gz 1216171206 688ed33b3a2a63bc1bce9da3c2d104de 145188 http://ftp.drupal.org/files/projects/devel-6.x-1.10.tar.gz tar.gz 688ed33b3a2a63bc1bce9da3c2d104de 145188 1216171206 http://ftp.drupal.org/files/projects/devel-6.x-1.10.zip zip c9e85aac38d3c2f18015c820ec325206 195931 1293230732 Release typeNew features Release typeBug fixes devel 6.x-1.9 6.x-1.9 DRUPAL-6--1-9 1 9 published http://drupal.org/node/270064 http://ftp.drupal.org/files/projects/devel-6.x-1.9.tar.gz 1213331113 8b88659f45e31d8c05fd6990c491ea2a 145153 http://ftp.drupal.org/files/projects/devel-6.x-1.9.tar.gz tar.gz 8b88659f45e31d8c05fd6990c491ea2a 145153 1213331113 http://ftp.drupal.org/files/projects/devel-6.x-1.9.zip zip 0d934dafa2cedd0d1bc5ec450cc9570f 195856 1293230729 Release typeNew features Release typeBug fixes devel 6.x-1.8 6.x-1.8 DRUPAL-6--1-8 1 8 published http://drupal.org/node/259615 http://ftp.drupal.org/files/projects/devel-6.x-1.8.tar.gz 1211039106 d8d8f4ee8dd61ccad625443ebafbe421 76796 http://ftp.drupal.org/files/projects/devel-6.x-1.8.tar.gz tar.gz d8d8f4ee8dd61ccad625443ebafbe421 76796 1211039106 http://ftp.drupal.org/files/projects/devel-6.x-1.8.zip zip 72b3468d765e1f9e827d9399f2d7218c 92505 1293230732 Release typeNew features Release typeBug fixes devel 6.x-1.7 6.x-1.7 DRUPAL-6--1-7 1 7 published http://drupal.org/node/237840 http://ftp.drupal.org/files/projects/devel-6.x-1.7.tar.gz 1206300306 b63eba9e1588baa2f8031589897eb376 72504 http://ftp.drupal.org/files/projects/devel-6.x-1.7.tar.gz tar.gz b63eba9e1588baa2f8031589897eb376 72504 1206300306 http://ftp.drupal.org/files/projects/devel-6.x-1.7.zip zip 9e4711ebace4e7cf0d7dc4e4a1386be3 87847 1293230726 devel 6.x-1.6 6.x-1.6 DRUPAL-6--1-6 1 6 published http://drupal.org/node/229249 http://ftp.drupal.org/files/projects/devel-6.x-1.6.tar.gz 1204515005 dc2fd4030922e17258a0fa886557aa9f 72109 http://ftp.drupal.org/files/projects/devel-6.x-1.6.tar.gz tar.gz dc2fd4030922e17258a0fa886557aa9f 72109 1204515005 http://ftp.drupal.org/files/projects/devel-6.x-1.6.zip zip 65cf489c56834571eaf43a30e87a25ef 87441 1293230732 Release typeBug fixes devel 6.x-1.5 6.x-1.5 DRUPAL-6--1-5 1 5 published http://drupal.org/node/227742 http://ftp.drupal.org/files/projects/devel-6.x-1.5.tar.gz 1204163705 4755dc85712f86c24a7615c83bf2d587 72424 http://ftp.drupal.org/files/projects/devel-6.x-1.5.tar.gz tar.gz 4755dc85712f86c24a7615c83bf2d587 72424 1204163705 http://ftp.drupal.org/files/projects/devel-6.x-1.5.zip zip 3def5895dcd04439f2e7c69e33ae3ef7 87108 1293230729 Release typeBug fixes devel 6.x-1.4 6.x-1.4 DRUPAL-6--1-4 1 4 published http://drupal.org/node/224889 http://ftp.drupal.org/files/projects/devel-6.x-1.4.tar.gz 1203609008 9361284333474368c49b807943e7eb09 72289 http://ftp.drupal.org/files/projects/devel-6.x-1.4.tar.gz tar.gz 9361284333474368c49b807943e7eb09 72289 1203609008 http://ftp.drupal.org/files/projects/devel-6.x-1.4.zip zip 0ee1899e32edcdb56ac13b067c8bb2aa 86956 1293230733 Release typeBug fixes devel 6.x-1.3 6.x-1.3 DRUPAL-6--1-3 1 3 published http://drupal.org/node/223052 http://ftp.drupal.org/files/projects/devel-6.x-1.3.tar.gz 1203291304 5dbb78276404810a219395d790bb349d 72231 http://ftp.drupal.org/files/projects/devel-6.x-1.3.tar.gz tar.gz 5dbb78276404810a219395d790bb349d 72231 1203291304 http://ftp.drupal.org/files/projects/devel-6.x-1.3.zip zip 293c305a758cd84b1a5b1e0f56c6883f 86893 1293230727 Release typeBug fixes devel 6.x-1.2 6.x-1.2 DRUPAL-6--1-2 1 2 published http://drupal.org/node/223032 http://ftp.drupal.org/files/projects/devel-6.x-1.2.tar.gz 1203288005 ec2d609a553289636f2388c54fc69e8b 72232 http://ftp.drupal.org/files/projects/devel-6.x-1.2.tar.gz tar.gz ec2d609a553289636f2388c54fc69e8b 72232 1203288005 http://ftp.drupal.org/files/projects/devel-6.x-1.2.zip zip 5855930a3427fdc2c3449aebf8ad68f9 86895 1293230730 Release typeBug fixes devel 6.x-1.1 6.x-1.1 DRUPAL-6--1-1 1 1 published http://drupal.org/node/222919 http://ftp.drupal.org/files/projects/devel-6.x-1.1.tar.gz 1203267606 2d1065298518decda8eb0d0473851ed1 72225 http://ftp.drupal.org/files/projects/devel-6.x-1.1.tar.gz tar.gz 2d1065298518decda8eb0d0473851ed1 72225 1203267606 http://ftp.drupal.org/files/projects/devel-6.x-1.1.zip zip f72a6870d67d929970c78151f7737ebc 86896 1293230733 Release typeNew features Release typeBug fixes devel 6.x-1.0 6.x-1.0 DRUPAL-6--1-0 1 0 published http://drupal.org/node/208665 http://ftp.drupal.org/files/projects/devel-6.x-1.0.tar.gz 1200012604 3dbd3cd4c15f001c1ac7067ca58c74ff 53802 http://ftp.drupal.org/files/projects/devel-6.x-1.0.tar.gz tar.gz 3dbd3cd4c15f001c1ac7067ca58c74ff 53802 1200012604 http://ftp.drupal.org/files/projects/devel-6.x-1.0.zip zip 10600179cfa8a301784839983fb49397 64756 1293230727 Release typeSecurity update devel 6.x-1.x-dev 6.x-1.x-dev DRUPAL-6--1 1 dev published http://drupal.org/node/224617 http://ftp.drupal.org/files/projects/devel-6.x-1.x-dev.tar.gz 1295179817 fec92fdc9dd40f34b2e7f9876fd44014 126254 http://ftp.drupal.org/files/projects/devel-6.x-1.x-dev.tar.gz tar.gz fec92fdc9dd40f34b2e7f9876fd44014 126254 1295179817 http://ftp.drupal.org/files/projects/devel-6.x-1.x-dev.zip zip 91255187be4c5cd7bacf7112e6b0189b 171436 1295179818 devel 6.x-0.2 6.x-0.2 DRUPAL-6--0-2 0 2 unpublished 1203287704 a071278bf76df5acf5320086f80ff4f5 72235 tar.gz a071278bf76df5acf5320086f80ff4f5 72235 1203287704 Release typeBug fixes drush-5.10.0/tests/drushScriptTest.php000066400000000000000000000013131222105546100177430ustar00rootroot00000000000000is_windows()) { $this->markTestSkipped('environment variable tests not currently functional on Windows.'); } $options = array( ); $env = array( 'PHP_OPTIONS' => '-d default_mimetype="text/drush"', ); $this->drush('ev', array('print ini_get("default_mimetype");'), $options, NULL, NULL, self::EXIT_SUCCESS, NULL, $env); $output = $this->getOutput(); $this->assertEquals('text/drush', $output); } } drush-5.10.0/tests/drush_testcase.inc000066400000000000000000000421461222105546100176040ustar00rootroot00000000000000_output = false; } /** * Assure that each class starts with an empty sandbox directory and * a clean environment - http://drupal.org/node/1103568. */ public static function setUpBeforeClass() { self::setUpFreshSandBox(); } /** * Remove any pre-existing sandbox, then create a new one. */ public static function setUpFreshSandBox() { $sandbox = UNISH_SANDBOX; if (file_exists($sandbox)) { unish_file_delete_recursive($sandbox); } $ret = mkdir($sandbox, 0777, TRUE); chdir(UNISH_SANDBOX); mkdir(getenv('HOME') . '/.drush', 0777, TRUE); mkdir($sandbox . '/etc/drush', 0777, TRUE); mkdir($sandbox . '/share/drush/commands', 0777, TRUE); if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { // Hack to make git use unix line endings on windows // We need it to make hashes of files pulled from git match ones hardcoded in tests if (!file_exists($sandbox . '\home')) { mkdir($sandbox . '\home'); } exec("git config --file $sandbox\\home\\.gitconfig core.autocrlf false", $output, $return); } } /** * Runs after all tests in a class are run. Remove sandbox directory. */ public static function tearDownAfterClass() { if (file_exists(UNISH_SANDBOX)) { unish_file_delete_recursive(UNISH_SANDBOX); } } /** * Print a log message to the console. * * @param string $message * @param string $type * Supported types are: * - notice * - verbose * - debug */ function log($message, $type = 'notice') { $line = "\nLog: $message\n"; switch ($this->log_level()) { case 'verbose': if (in_array($type, array('notice', 'verbose'))) print $line; break; case 'debug': print $line; break; default: if ($type == 'notice') print $line; break; } } function log_level() { if (in_array('--debug', $_SERVER['argv'])) { return 'debug'; } elseif (in_array('--verbose', $_SERVER['argv'])) { return 'verbose'; } } public static function is_windows() { return (strtoupper(substr(PHP_OS, 0, 3)) == "WIN"); } public static function get_tar_executable() { return self::is_windows() ? "bsdtar.exe" : "tar"; } /** * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3). * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a * proper drive path, still with Unix slashes (c:/dir1). * * @copied from Drush's environment.inc */ function convert_path($path) { $path = str_replace('\\','/', $path); $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path); return $path; } /** * Borrowed from Drush. * Checks operating system and returns * supported bit bucket folder. */ function bit_bucket() { if (!$this->is_windows()) { return '/dev/null'; } else { return 'nul'; } } public static function escapeshellarg($arg) { // Short-circuit escaping for simple params (keep stuff readable) if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) { return $arg; } elseif (self::is_windows()) { return self::_escapeshellarg_windows($arg); } else { return escapeshellarg($arg); } } public static function _escapeshellarg_windows($arg) { // Double up existing backslashes $arg = preg_replace('/\\\/', '\\\\\\\\', $arg); // Double up double quotes $arg = preg_replace('/"/', '""', $arg); // Double up percents. // $arg = preg_replace('/%/', '%%', $arg); // Add surrounding quotes. $arg = '"' . $arg . '"'; return $arg; } /** * Helper function to generate a random string of arbitrary length. * * Copied from drush_generate_password(), which is otherwise not available here. * * @param $length * Number of characters the generated string should contain. * @return * The generated string. */ public function randomString($length = 10) { // This variable contains the list of allowable characters for the // password. Note that the number 0 and the letter 'O' have been // removed to avoid confusion between the two. The same is true // of 'I', 1, and 'l'. $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Zero-based count of characters in the allowable list: $len = strlen($allowable_characters) - 1; // Declare the password as a blank string. $pass = ''; // Loop the number of times specified by $length. for ($i = 0; $i < $length; $i++) { // Each iteration, pick a random character from the // allowable string and append it to the password: $pass .= $allowable_characters[mt_rand(0, $len)]; } return $pass; } /** * Accessor for the last output. * @return string Output as text. * @access public */ function getOutput() { return implode("\n", $this->_output); } /** * Accessor for the last output. * @return array Output as array of lines. * @access public */ function getOutputAsList() { return $this->_output; } function webroot() { return UNISH_SANDBOX . '/web'; } function directory_cache($subdir = '') { return getenv('CACHE_PREFIX') . '/' . $subdir; } function db_url($env) { return substr(UNISH_DB_URL, 0, 6) == 'sqlite' ? "sqlite://sites/$env/files/unish.sqlite" : UNISH_DB_URL . '/unish_' . $env; } function setUpDrupal($num_sites = 1, $install = FALSE, $version_string = UNISH_DRUPAL_MAJOR_VERSION, $profile = NULL) { $sites_subdirs_all = array('dev', 'stage', 'prod', 'retired', 'elderly', 'dead', 'dust'); $sites_subdirs = array_slice($sites_subdirs_all, 0, $num_sites); $root = $this->webroot(); if (is_null($profile)) { $profile = substr($version_string, 0, 1) >= 7 ? 'testing' : 'default'; } $db_driver = parse_url(UNISH_DB_URL, PHP_URL_SCHEME); $cache_keys = array($num_sites, $install ? 'install' : 'noinstall', $version_string, $profile, $db_driver); $source = $this->directory_cache('environments') . '/' . implode('-', $cache_keys) . '.tar.gz'; if (file_exists($source)) { $this->log('Cache HIT. Environment: ' . $source, 'verbose'); $this->drush('archive-restore', array($source), array('destination' => $root, 'overwrite' => NULL)); } else { $this->log('Cache MISS. Environment: ' . $source, 'verbose'); // Build the site(s), install (if needed), then cache. foreach ($sites_subdirs as $subdir) { $this->fetchInstallDrupal($subdir, $install, $version_string, $profile); } $options = array( 'destination' => $source, 'root' => $root, 'uri' => reset($sites_subdirs), 'overwrite' => NULL, ); $this->drush('archive-dump', array('@sites'), $options); } // Stash details about each site. foreach ($sites_subdirs as $subdir) { $this->sites[$subdir] = array( 'db_url' => $this->db_url($subdir), ); // Make an alias for the site $alias_definition = array($subdir => array('root' => $root, 'uri' => $subdir)); file_put_contents(UNISH_SANDBOX . '/etc/drush/' . $subdir . '.alias.drushrc.php', $this->file_aliases($alias_definition)); } return $this->sites; } function fetchInstallDrupal($env = 'dev', $install = FALSE, $version_string = UNISH_DRUPAL_MAJOR_VERSION, $profile = NULL) { $root = $this->webroot(); $site = "$root/sites/$env"; // Download Drupal if not already present. if (!file_exists($root)) { $options = array( 'destination' => UNISH_SANDBOX, 'drupal-project-rename' => 'web', 'yes' => NULL, 'quiet' => NULL, 'cache' => NULL, ); $this->drush('pm-download', array("drupal-$version_string"), $options); mkdir(UNISH_SANDBOX . '/web/sites/all/drush'); } // If specified, install Drupal as a multi-site. if ($install) { $options = array( 'root' => $root, 'db-url' => $this->db_url($env), 'sites-subdir' => $env, 'yes' => NULL, 'quiet' => NULL, ); $this->drush('site-install', array($profile), $options); // Give us our write perms back. chmod($site, 0777); } else { mkdir($site); touch("$site/settings.php"); } } } abstract class Drush_CommandTestCase extends Drush_TestCase { // Unix exit codes. const EXIT_SUCCESS = 0; const EXIT_ERROR = 1; /* * An array of Drupal sites that are setup in the drush-sandbox. */ var $sites; /** * Actually runs the command. Does not trap the error stream output as this * need PHP 4.3+. * * @param string $command * The actual command line to run. * @param integer $expected_return * The return code to expect * @return integer * Exit code. Usually self::EXIT_ERROR or self::EXIT_SUCCESS. */ function execute($command, $expected_return = self::EXIT_SUCCESS, $env = array()) { $this->_output = FALSE; $return = 1; $this->log("Executing: $command", 'notice'); // Apply the environment variables we need for our test // to the head of the command $prefix = ''; foreach ($env as $env_name => $env_value) { $prefix .= $env_name . '=' . self::escapeshellarg($env_value) . ' '; } exec($prefix . $command, $this->_output, $return); $this->assertEquals($expected_return, $return, 'Unexpected exit code: ' . $command); return $return; } /** * Invoke drush in via execute(). * * @param command * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. * @param args * Command arguments. * @param $options * An associative array containing options. * @param $site_specification * A site alias or site specification. Include the '@' at start of a site alias. * @param $cd * A directory to change into before executing. * @param $expected_return * The expected exit code. Usually self::EXIT_ERROR or self::EXIT_SUCCESS. * @param $suffix * Any code to append to the command. For example, redirection like 2>&1. * @return integer * An exit code. */ function drush($command, array $args = array(), array $options = array(), $site_specification = NULL, $cd = NULL, $expected_return = self::EXIT_SUCCESS, $suffix = NULL, $env = array()) { $global_option_list = array('simulate', 'root', 'uri', 'include', 'config', 'alias-path', 'ssh-options'); // insert "cd ... ; drush" $cmd[] = $cd ? sprintf('cd %s &&', self::escapeshellarg($cd)) : NULL; $cmd[] = UNISH_DRUSH; // insert global options foreach ($options as $key => $value) { if (in_array($key, $global_option_list)) { unset($options[$key]); if (is_null($value)) { $cmd[] = "--$key"; } else { $cmd[] = "--$key=" . self::escapeshellarg($value); } } } if ($level = $this->log_level()) { $cmd[] = '--' . $level; } $cmd[] = "--nocolor"; // insert site specification and drush command $cmd[] = empty($site_specification) ? NULL : self::escapeshellarg($site_specification); $cmd[] = $command; // insert drush command arguments foreach ($args as $arg) { $cmd[] = self::escapeshellarg($arg); } // insert drush command options foreach ($options as $key => $value) { if (is_null($value)) { $cmd[] = "--$key"; } else { $cmd[] = "--$key=" . self::escapeshellarg($value); } } $cmd[] = $suffix; $exec = array_filter($cmd, 'strlen'); // Remove NULLs return $this->execute(implode(' ', $exec), $expected_return, $env); } function drush_major_version() { static $major; if (!isset($major)) { $this->drush('version', array('drush_version'), array('pipe' => NULL)); $version = $this->getOutput(); list($major) = explode('.', $version); } return $major; } /* * Prepare the contents of an aliases file. */ function file_aliases($aliases) { foreach ($aliases as $name => $alias) { $records[] = sprintf('$aliases[\'%s\'] = %s;', $name, var_export($alias, TRUE)); } $contents = ">>%s<<setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), ); // Create two field instances on article content type. $this->drush('field-create', array('user', 'city,text,text_textfield', 'subtitle,text,text_textfield'), $options + array('entity_type' => 'user')); $output = $this->getOutput(); list($city, $subtitle) = explode(' ', $output); $url = parse_url($subtitle); $this->assertEquals('/admin/config/people/accounts/fields/subtitle', $url['path']); // Assure that the second field instance was created correctly (subtitle). $this->verifyInstance('subtitle', $options); // Assure that field update URL looks correct. $this->drush('field-update', array('subtitle'), $options); $output = $this->getOutput(); $url = parse_url($this->getOutput()); $this->assertEquals('/admin/config/people/accounts/fields/subtitle', $url['path']); // Assure that field-clone actually clones. $this->drush('field-clone', array('subtitle', 'subtitlecloned'), $options); $this->verifyInstance('subtitlecloned', $options); // Assure that delete works. $this->drush('field-delete', array('subtitlecloned'), $options); $this->verifyInstance('subtitlecloned', $options, FALSE); } function verifyInstance($name, $options, $expected = TRUE) { $this->drush('field-info', array('fields'), $options + array('pipe' => NULL)); $output = $this->getOutputAsList(); $found = FALSE; foreach($output as $row) { $columns = explode(',', $row); if ($columns[0] == $name) { $this->assertEquals('text', $columns[1], $name . ' field is of type=text.'); $this->assertEquals('user', $columns[2], $name . ' field was added to user bundle.'); $found = TRUE; break; } } if ($expected) { $this->assertTrue($found, $name . ' field was created.'); } else { $this->assertFalse($found, $name . ' field was not present.'); } } } drush-5.10.0/tests/filesystemTest.php000066400000000000000000000025511222105546100176220ustar00rootroot00000000000000is_windows()) { $this->markTestSkipped("s-bit test doesn't apply on Windows."); } if (is_null(UNISH_USERGROUP)) { $this->markTestSkipped("s-bit test skipped because of UNISH_USERGROUP was not set."); } $dest = UNISH_SANDBOX . '/test-filesystem-sbit'; mkdir($dest); chgrp($dest, UNISH_USERGROUP); chmod($dest, 02755); // rwxr-sr-x $this->drush('pm-download', array('devel'), array('cache' => NULL, 'skip' => NULL, 'destination' => $dest)); $group = posix_getgrgid(filegroup($dest . '/devel/README.txt')); $this->assertEquals($group['name'], UNISH_USERGROUP, 'Group is preserved.'); $perms = fileperms($dest . '/devel') & 02000; $this->assertEquals($perms, 02000, 's-bit is preserved.'); } public function testExecuteBits() { if ($this->is_windows()) { $this->markTestSkipped("execute bit test doesn't apply on Windows."); } $dest = UNISH_SANDBOX . '/test-filesystem-execute'; mkdir($dest); $this->execute(sprintf("git clone https://github.com/drush-ops/drush.git %s", $dest . '/drush')); $perms = fileperms($dest . '/drush/drush') & 0111; $this->assertEquals($perms, 0111, 'Execute permission is preserved.'); } } drush-5.10.0/tests/generateMakeTest.php000066400000000000000000000043041222105546100200240ustar00rootroot00000000000000setUpDrupal(1, TRUE, UNISH_DRUPAL_MAJOR_VERSION, 'standard'); $options = array( 'yes' => NULL, 'pipe' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), 'cache' => NULL, 'invoke' => NULL, // Don't validate options ); $this->drush('pm-download', array('omega', 'context', 'delta', 'adaptive_image', 'ctools'), $options); $this->drush('pm-enable', array('omega', 'context', 'delta', 'adaptive_image'), $options); $makefile = UNISH_SANDBOX . '/dev.make'; // First generate a simple makefile with no version information $this->drush('generate-makefile', array($makefile), array('exclude-versions' => NULL) + $options); $expected = '; This file was auto-generated by drush make core = 7.x api = 2 projects[] = "drupal" ; Modules projects[] = "adaptive_image" projects[] = "ctools" projects[] = "context" projects[] = "delta" ; Themes projects[] = "omega"'; $actual = trim(file_get_contents($makefile)); $this->assertEquals($expected, $actual); // Download a module to a 'contrib' directory to test the subdir feature mkdir($this->webroot() + '/sites/all/modules/contrib'); $this->drush('pm-download', array('views'), array('destination' => 'sites/all/modules/contrib') + $options); $this->drush('pm-enable', array('views'), $options); $this->drush('generate-makefile', array($makefile), array('exclude-versions' => NULL) + $options); $expected = '; This file was auto-generated by drush make core = 7.x api = 2 projects[] = "drupal" ; Modules projects[] = "adaptive_image" projects[] = "ctools" projects[] = "context" projects[] = "delta" projects[views][subdir] = "contrib" ; Themes projects[] = "omega"'; $actual = trim(file_get_contents($makefile)); $this->assertEquals($expected, $actual); // Generate a makefile with version numbers. $this->drush('generate-makefile', array($makefile), $options); $actual = file_get_contents($makefile); $this->assertContains('projects[adaptive_image][version] = "', $actual); } } drush-5.10.0/tests/hooks/000077500000000000000000000000001222105546100152055ustar00rootroot00000000000000drush-5.10.0/tests/hooks/magic_help_alter/000077500000000000000000000000001222105546100204645ustar00rootroot00000000000000drush-5.10.0/tests/hooks/magic_help_alter/magic.drush.inc000066400000000000000000000002301222105546100233560ustar00rootroot00000000000000setUpDrupal(1, TRUE, UNISH_DRUPAL_MAJOR_VERSION, 'standard'); $options = array( 'yes' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), ); // Test that "drush image-flush thumbnail" deletes derivatives created by the thumbnail image style. $style_name = 'thumbnail'; $php = "\$image_style = image_style_load('--stylename--');" . "image_style_create_derivative(\$image_style, '" . $options['root'] . "/themes/bartik/logo.png', 'public://styles/--stylename--/logo.png');"; $this->drush('php-eval', array(str_replace('--stylename--', $style_name, $php)), $options); $this->assertFileExists($options['root'] . '/sites/' . key($sites) . '/files/styles/' . $style_name . '/logo.png'); $this->drush('image-flush', array($style_name), $options); $this->assertFileNotExists($options['root'] . '/sites/' . key($sites) . '/files/styles/' . $style_name . '/logo.png'); // Check that "drush image-flush --all" deletes all image styles by creating two different ones and testing its // existance afterwards. $style_name = 'thumbnail'; $this->drush('php-eval', array(str_replace('--stylename--', $style_name, $php)), $options); $this->assertFileExists($options['root'] . '/sites/' . key($sites) . '/files/styles/' . $style_name . '/logo.png'); $style_name = 'medium'; $this->drush('php-eval', array(str_replace('--stylename--', $style_name, $php)), $options); $this->assertFileExists($options['root'] . '/sites/' . key($sites) . '/files/styles/' . $style_name . '/logo.png'); $this->drush('image-flush', array(), array('all' => TRUE) + $options); $this->assertFileNotExists($options['root'] . '/sites/' . key($sites) . '/files/styles/thumbnail/logo.png'); $this->assertFileNotExists($options['root'] . '/sites/' . key($sites) . '/files/styles/medium/logo.png'); } } drush-5.10.0/tests/makeTest.php000066400000000000000000000452371222105546100163630ustar00rootroot00000000000000makefile_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'makefiles'; } /** * Run a given makefile test. * * @param $test * The test makefile to run, as defined by $this->getMakefile(); */ private function runMakefileTest($test) { $default_options = array( 'test' => NULL, 'md5' => 'print', ); $config = $this->getMakefile($test); $options = array_merge($config['options'], $default_options); $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $return = !empty($config['fail']) ? self::EXIT_ERROR : self::EXIT_SUCCESS; $this->drush('make', array($makefile), $options, NULL, NULL, $return); // Check the log for the build hash if this test should pass. if (empty($config['fail'])) { $output = $this->getOutput(); $this->assertContains($config['md5'], $output, $config['name'] . ' - build md5 matches expected value: ' . $config['md5']); } } function testMakeGet() { $this->runMakefileTest('get'); } function testMakeGit() { $this->runMakefileTest('git'); } function testMakeGitSimple() { $this->runMakefileTest('git-simple'); } function testMakeNoPatchTxt() { $this->runMakefileTest('no-patch-txt'); } function testMakePatch() { $this->runMakefileTest('patch'); } function testMakeInclude() { $this->runMakefileTest('include'); } function testMakeRecursion() { $this->runMakefileTest('recursion'); } function testMakeSvn() { // Silently skip svn test if svn is not installed. exec('which svn', $output, $whichSvnErrorCode); if (!$whichSvnErrorCode) { $this->runMakefileTest('svn'); } else { $this->markTestSkipped('svn command not available.'); } } function testMakeBzr() { // Silently skip bzr test if bzr is not installed. exec('which bzr', $output, $whichBzrErrorCode); if (!$whichBzrErrorCode) { $this->runMakefileTest('bzr'); } else { $this->markTestSkipped('bzr command is not available.'); } } /** * Translations can change arbitrarily, so these test for the existence of .po * files, rather than trying to match a build hash. */ function testMakeTranslations() { $config = $this->getMakefile('translations'); $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $install_directory = UNISH_SANDBOX . '/translations'; $this->drush('make', array($makefile, $install_directory), $config['options']); $po_files = array( 'sites/all/modules/token/translations/pt-br.po', 'sites/all/modules/token/translations/es.po', ); foreach ($po_files as $po_file) { $this->assertFileExists($install_directory . '/' . $po_file); } } /** * Translations can change arbitrarily, so these test for the existence of .po * files, rather than trying to match a build hash. */ function testMakeTranslationsInside() { $config = $this->getMakefile('translations-inside'); $makefile = $this->makefile_path . '/' . $config['makefile']; $install_directory = UNISH_SANDBOX . '/translations-inside'; $this->drush('make', array($makefile, $install_directory)); $po_files = array( 'profiles/default/translations/pt-br.po', 'profiles/default/translations/es.po', 'sites/all/modules/token/translations/pt-br.po', 'sites/all/modules/token/translations/es.po', 'modules/system/translations/pt-br.po', 'modules/system/translations/es.po', ); foreach ($po_files as $po_file) { $this->assertFileExists($install_directory . '/' . $po_file); } } /** * Translations can change arbitrarily, so these test for the existence of .po * files, rather than trying to match a build hash. */ function testMakeTranslationsInside7() { $config = $this->getMakefile('translations-inside7'); $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $install_directory = UNISH_SANDBOX . '/translations-inside7'; $this->drush('make', array($makefile, $install_directory)); $po_files = array( 'profiles/minimal/translations/pt-br.po', 'profiles/minimal/translations/es.po', 'profiles/testing/translations/pt-br.po', 'profiles/testing/translations/es.po', 'profiles/standard/translations/pt-br.po', 'profiles/standard/translations/es.po', 'sites/all/modules/token/translations/pt-br.po', 'sites/all/modules/token/translations/es.po', 'modules/system/translations/pt-br.po', 'modules/system/translations/es.po', ); foreach ($po_files as $po_file) { $this->assertFileExists($install_directory . '/' . $po_file); } } function testMakeContribDestination() { $this->runMakefileTest('contrib-destination'); } function testMakeDefaults() { $this->runMakefileTest('defaults'); } function testMakeFile() { $this->runMakefileTest('file'); } function testMakeBZ2() { // Silently skip bz2 test if bz2 is not installed. exec('which bzip2', $output, $whichBzip2ErrorCode); if (!$whichBzip2ErrorCode) { $this->runMakefileTest('bz2'); } else { $this->markTestSkipped('bzip2 command not available.'); } } function testMakeBZ2SingleFile() { // Silently skip bz2 test if bz2 is not installed. exec('which bzip2', $output, $whichBzip2ErrorCode); if (!$whichBzip2ErrorCode) { $this->runMakefileTest('bz2-singlefile'); } else { $this->markTestSkipped('bzip2 command not available.'); } } function testMakeGZip() { // Silently skip gzip test if gzip is not installed. exec('which gzip', $output, $whichGzipErrorCode); if (!$whichGzipErrorCode) { $this->runMakefileTest('gzip'); } else { $this->markTestSkipped('gzip command not available.'); } } function testMakeSubtree() { $config = $this->getMakefile('subtree'); $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $install_directory = UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'subtree'; $this->drush('make', array('--no-core', $makefile, $install_directory)); $files['nivo-slider'] = array( 'exists' => array( 'jquery.nivo.slider.js', 'jquery.nivo.slider.pack.js', 'license.txt', 'nivo-slider.css', 'README', ), 'notexists' => array( '__MACOSX', 'nivo-slider', ), ); $files['fullcalendar'] = array( 'exists' => array( 'fullcalendar.css', 'fullcalendar.js', 'fullcalendar.min.js', 'fullcalendar.print.css', 'gcal.js', ), 'notexists' => array( 'changelog.txt', 'demos', 'fullcalendar', 'GPL-LICENSE.txt', 'jquery', 'MIT-LICENSE.txt', ), ); $basedir = $install_directory . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . 'all' . DIRECTORY_SEPARATOR . 'libraries'; foreach ($files as $lib => $details) { $dir = $basedir . DIRECTORY_SEPARATOR . $lib; if (!empty($details['exists'])) { foreach ($details['exists'] as $file) { $this->assertFileExists($dir . DIRECTORY_SEPARATOR . $file); } } if (!empty($details['notexists'])) { foreach ($details['notexists'] as $file) { $this->assertFileNotExists($dir . DIRECTORY_SEPARATOR . $file); } } } } function testMakeMd5Succeed() { $this->runMakefileTest('md5-succeed'); } function testMakeMd5Fail() { $this->runMakefileTest('md5-fail'); } function testMakeIgnoreChecksums() { $this->runMakefileTest('ignore-checksums'); } /** * Test .info file writing and the use of a git reference cache for * git downloads. */ function testInfoFileWritingGit() { // Use the git-simple.make file. $config = $this->getMakefile('git-simple'); $options = array('no-core' => NULL); $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $this->drush('make', array($makefile, UNISH_SANDBOX . '/test-build'), $options); // Test cck_signup.info file. $this->assertFileExists(UNISH_SANDBOX . '/test-build/sites/all/modules/cck_signup/cck_signup.info'); $contents = file_get_contents(UNISH_SANDBOX . '/test-build/sites/all/modules/cck_signup/cck_signup.info'); $this->assertContains('; Information added by drush on ' . date('Y-m-d'), $contents); $this->assertContains('version = "2fe932c"', $contents); $this->assertContains('project = "cck_signup"', $contents); // Verify that a reference cache was created. $cache_dir = UNISH_CACHE . DIRECTORY_SEPARATOR . 'cache'; $this->assertFileExists($cache_dir . '/git/cck_signup-' . md5('http://git.drupal.org/project/cck_signup.git')); // Test context_admin.info file. $this->assertFileExists(UNISH_SANDBOX . '/test-build/sites/all/modules/context_admin/context_admin.info'); $contents = file_get_contents(UNISH_SANDBOX . '/test-build/sites/all/modules/context_admin/context_admin.info'); $this->assertContains('; Information added by drush on ' . date('Y-m-d'), $contents); $this->assertContains('version = "eb9f05e"', $contents); $this->assertContains('project = "context_admin"', $contents); // Verify git reference cache exists. $this->assertFileExists($cache_dir . '/git/context_admin-' . md5('http://git.drupal.org/project/context_admin.git')); // Text caption_filter .info rewrite. $this->assertFileExists(UNISH_SANDBOX . '/test-build/sites/all/modules/contrib/caption_filter/caption_filter.info'); $contents = file_get_contents(UNISH_SANDBOX . '/test-build/sites/all/modules/contrib/caption_filter/caption_filter.info'); $this->assertContains('; Information added by drush on ' . date('Y-m-d'), $contents); $this->assertContains('version = "7.x-1.2+0-dev"', $contents); $this->assertContains('project = "caption_filter"', $contents); } function testMakeFileExtract() { $this->runMakefileTest('file-extract'); } function testMakeLimitProjects() { $this->runMakefileTest('limit-projects'); $this->runMakefileTest('limit-projects-multiple'); } function testMakeLimitLibraries() { $this->runMakefileTest('limit-libraries'); $this->runMakefileTest('limit-libraries-multiple'); } /** * Test that make_move_build() doesn't wipe out directories that it shouldn't. */ function testMakeMoveBuild() { // Manually download a module. $this->drush('pm-download', array('cck_signup'), array('destination' => UNISH_SANDBOX . '/sites/all/modules/contrib', 'yes' => NULL)); // Build a make file. $config = $this->getMakefile('contrib-destination'); $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $this->drush('make', array($makefile, '.'), $config['options']); // Verify that the manually downloaded module still exists. $this->assertFileExists(UNISH_SANDBOX . '/sites/all/modules/contrib/cck_signup/README.txt'); } /** * Test that a distribution can be used as a "core" project. */ function testMakeUseDistributionAsCore() { $this->runMakefileTest('use-distribution-as-core'); } function getMakefile($key) { static $tests = array( 'get' => array( 'name' => 'Test GET retrieval of projects', 'makefile' => 'get.make', 'build' => TRUE, 'md5' => '4bf18507da89bed601548210c22a3bed', 'options' => array('no-core' => NULL), ), 'git' => array( 'name' => 'GIT integration', 'makefile' => 'git.make', 'build' => TRUE, 'md5' => '4c80d78b50c89b5ba11a997bafec2b43', 'options' => array('no-core' => NULL, 'no-gitinfofile' => NULL), ), 'git-simple' => array( 'name' => 'Simple git integration', 'makefile' => 'git-simple.make', 'build' => TRUE, 'md5' => '0147681209adef163a8ac2c0cff2a07e', 'options' => array('no-core' => NULL, 'no-gitinfofile' => NULL), ), 'no-patch-txt' => array( 'name' => 'Test --no-patch-txt option', 'makefile' => 'patches.make', 'build' => TRUE, 'md5' => 'e43b25505a5edfcdf25b4eaa064978b2', 'options' => array('no-core' => NULL, 'no-patch-txt' => NULL), ), 'patch' => array( 'name' => 'Test patching and writing of PATCHES.txt file', 'makefile' => 'patches.make', 'build' => TRUE, 'md5' => '56f1613fc8b6a9f03ab62cfa0300df4c', 'options' => array('no-core' => NULL), ), 'include' => array( 'name' => 'Including files and property overrides', 'makefile' => 'include.make', 'build' => TRUE, 'md5' => 'e2e230ec5eccaf5618050559ab11510d', 'options' => array(), ), 'recursion' => array( 'name' => 'Recursion', 'makefile' => 'recursion.make', 'build' => TRUE, 'md5' => 'cd095bd6dadb2f0d3e81f85f13685372', 'options' => array( 'no-core' => NULL, 'contrib-destination' => 'profiles/drupal_forum', ), ), 'svn' => array( 'name' => 'SVN', 'makefile' => 'svn.make', 'build' => TRUE, 'md5' => '0cb28a15958d7fc4bbf8bf6b00bc6514', 'options' => array('no-core' => NULL), ), 'bzr' => array( 'name' => 'Bzr', 'makefile' => 'bzr.make', 'build' => TRUE, 'md5' => '272e2b9bb27794c54396f2f03c159725', 'options' => array(), ), 'translations' => array( 'name' => 'Translation downloads', 'makefile' => 'translations.make', 'options' => array( 'translations' => 'es,pt-br', 'no-core' => NULL, ), ), 'translations-inside' => array( 'name' => 'Translation downloads inside makefile', 'makefile' => 'translations-inside.make', ), 'translations-inside7' => array( 'name' => 'Translation downloads inside makefile, core 7.x', 'makefile' => 'translations-inside7.make', ), 'contrib-destination' => array( 'name' => 'Contrib-destination attribute', 'makefile' => 'contrib-destination.make', 'build' => TRUE, 'md5' => 'd615d004adfa8ebfe44e91119b88389c', 'options' => array('no-core' => NULL, 'contrib-destination' => '.'), ), 'file' => array( 'name' => 'File extraction', 'makefile' => 'file.make', 'build' => TRUE, 'md5' => '4e9883d6f9f6572af287635689c2545d', 'options' => array('no-core' => NULL), ), 'defaults' => array( 'name' => 'Test defaults array.', 'makefile' => 'defaults.make', 'build' => TRUE, 'md5' => 'e6c0d6b37cd8573cbd188742b95a274e', 'options' => array('no-core' => NULL, 'contrib-destination' => '.'), ), 'bz2' => array( 'name' => 'bzip2', 'makefile' => 'bz2.make', 'build' => TRUE, 'md5' => '5ec081203131a1a3277c8b23f9ddb995', 'options' => array('no-core' => NULL), ), 'bz2-singlefile' => array( 'name' => 'bzip2 single file', 'makefile' => 'bz2-singlefile.make', 'build' => TRUE, 'md5' => '4f9d57f6caaf6ece0526d867327621cc', 'options' => array('no-core' => NULL), ), 'gzip' => array( 'name' => 'gzip', 'makefile' => 'gzip.make', 'build' => TRUE, 'md5' => '615975484966c36f4c9186601afd61e0', 'options' => array('no-core' => NULL), ), 'subtree' => array( 'name' => 'Use subtree from downloaded archive', 'makefile' => 'subtree.make', 'build' => TRUE, 'md5' => 'db3770d8b4c9ce77510cbbcc566da9b8', 'options' => array('no-core' => NULL), ), 'md5-succeed' => array( 'name' => 'MD5 validation', 'makefile' => 'md5-succeed.make', 'build' => TRUE, 'md5' => 'f76ec174a775ce67f8e9edcb02336ef2', 'options' => array('no-core' => NULL), ), 'md5-fail' => array( 'name' => 'Failed MD5 validation test', 'makefile' => 'md5-fail.make', 'build' => FALSE, 'md5' => FALSE, 'options' => array('no-core' => NULL), 'fail' => TRUE, ), 'ignore-checksums' => array( 'name' => 'Ignore invalid checksum/s', 'makefile' => 'md5-fail.make', 'build' => TRUE, 'md5' => 'f76ec174a775ce67f8e9edcb02336ef2', 'options' => array('no-core' => NULL, 'ignore-checksums' => NULL), ), 'file-extract' => array( 'name' => 'Extract archives', 'makefile' => 'file-extract.make', 'build' => TRUE, 'md5' => 'f92471fb7979e45d2554c61314ac6236', // @todo This test often fails with concurrency set to more than one. 'options' => array('no-core' => NULL, 'concurrency' => 1), ), 'limit-projects' => array( 'name' => 'Limit projects downloaded', 'makefile' => 'limited-projects-libraries.make', 'build' => TRUE, 'md5' => '3149650120e541d7d0fa577eef0ee9a3', 'options' => array('no-core' => NULL, 'projects' => 'boxes'), ), 'limit-projects-multiple' => array( 'name' => 'Limit multiple projects downloaded', 'makefile' => 'limited-projects-libraries.make', 'build' => TRUE, 'md5' => 'ef8996c4d6c6f0d229e2237c73860071', 'options' => array('no-core' => NULL, 'projects' => 'boxes,admin_menu'), ), 'limit-libraries' => array( 'name' => 'Limit libraries downloaded', 'makefile' => 'limited-projects-libraries.make', 'build' => TRUE, 'md5' => 'cb0da4465d86eb34cafb167787862eb6', 'options' => array('no-core' => NULL, 'libraries' => 'drush_make'), ), 'limit-libraries-multiple' => array( 'name' => 'Limit multiple libraries downloaded', 'makefile' => 'limited-projects-libraries.make', 'build' => TRUE, 'md5' => '7c10e6fc65728a77a2b0aed4ec2a29cd', 'options' => array('no-core' => NULL, 'libraries' => 'drush_make,token'), ), 'use-distribution-as-core' => array( 'name' => 'Use distribution as core', 'makefile' => 'use-distribution-as-core.make', 'build' => TRUE, 'md5' => '643a603025a20d498eb583a1e7970bad', 'options' => array(), ), ); return $tests[$key]; } } drush-5.10.0/tests/makefiles/000077500000000000000000000000001222105546100160225ustar00rootroot00000000000000drush-5.10.0/tests/makefiles/bz2-singlefile.make000066400000000000000000000006221222105546100214750ustar00rootroot00000000000000api = 2 core = 6.x ; wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2 contains the single file "wkhtmltopdf-amd64". ; This should move that single file to sites/all/libraries/wkhtmltopdf . libraries[wkhtmltopdf][destination] = libraries libraries[wkhtmltopdf][download][type] = get libraries[wkhtmltopdf][download][url] = http://wkhtmltopdf.googlecode.com/files/wkhtmltopdf-0.11.0_rc1-static-amd64.tar.bz2 drush-5.10.0/tests/makefiles/bz2.make000066400000000000000000000004721222105546100173610ustar00rootroot00000000000000core = 6.x api = 2 ; bison-1.30.tar.bz2 contains wrapper folder "bison-1.30/". ; This should move that wrapper folder to sites/all/libraries/bison/ . libraries[bison][destination] = libraries libraries[bison][download][type] = get libraries[bison][download][url] = http://ftp.gnu.org/gnu/bison/bison-1.30.tar.bz2 drush-5.10.0/tests/makefiles/bzr.make000066400000000000000000000002411222105546100174530ustar00rootroot00000000000000core = 6.x api = 2 projects[mv][type] = "core" projects[mv][download][type] = "bzr" projects[mv][download][url] = "lp:mv" projects[mv][download][revision] = 30 drush-5.10.0/tests/makefiles/contrib-destination.make000066400000000000000000000002401222105546100226340ustar00rootroot00000000000000core = "6.x" api = 2 projects[boxes][subdir] = "contrib" projects[boxes][version] = "1.0" projects[admin][subdir] = "contrib" projects[admin][version] = "2.0"drush-5.10.0/tests/makefiles/defaults.make000066400000000000000000000003471222105546100204740ustar00rootroot00000000000000core = "6.x" api = 2 defaults[projects][subdir] = "contrib" projects[boxes][version] = "1.0" projects[admin][version] = "2.0" ; Override project default projects[devel][version] = "1.27" projects[devel][subdir] = "development" drush-5.10.0/tests/makefiles/file-extract.make000066400000000000000000000014451222105546100212540ustar00rootroot00000000000000core = 6.x api = 2 projects[admin_menu][download][type] = "file" projects[admin_menu][download][url] = "http://ftp.drupal.org/files/projects/admin_menu-6.x-1.0-beta.zip?param=value" projects[admin_menu][download][md5] = "673ec1bb431113142016d5eb0a0ef77f" projects[devel][download][type] = "file" projects[devel][download][url] = "http://ftp.drupal.org/files/projects/devel-6.x-1.0.tar.gz?param=value" projects[devel][download][md5] = "3dbd3cd4c15f001c1ac7067ca58c74ff" libraries[vegas][download][type] = "get" libraries[vegas][download][url] = "https://github.com/jaysalvat/vegas/zipball/v1.2" libraries[autopager][download][type] = "get" libraries[autopager][download][url] = "http://lagoscript.org/files/jquery/autopager/jquery.autopager-1.0.0.js" libraries[autopager][directory_name] = "autopager" drush-5.10.0/tests/makefiles/file.make000066400000000000000000000010061222105546100175750ustar00rootroot00000000000000core = 6.x api = 2 libraries[drush_make][download][type] = "file" libraries[drush_make][download][url] = "http://drupalcode.org/project/drush_make.git/blob_plain/8d4e770:/README.txt" libraries[drush_make][download][filename] = "README.txt" ; Single file download libraries[responsiveslides][download][type] = "get" libraries[responsiveslides][download][url] = "https://raw.github.com/viljamis/ResponsiveSlides.js/v1.54/responsiveslides.min.js" libraries[responsiveslides][download][filename] = "responsiveslides.js" drush-5.10.0/tests/makefiles/get.make000066400000000000000000000005141222105546100174400ustar00rootroot00000000000000core = 6.x api = 2 ; Tarball file download libraries[drush_make][download][type] = file libraries[drush_make][download][url] = http://ftp.drupal.org/files/projects/drush_make-6.x-2.0-beta8.tar.gz libraries[drush_make][directory_name] = drush_make libraries[drush_make][destination] = libraries libraries[drush_make][lock] = Locked drush-5.10.0/tests/makefiles/git-simple.make000066400000000000000000000011241222105546100207310ustar00rootroot00000000000000core = 6.x api = 2 ; Test that make defaults to download type of git if any download ; parameters are present. projects[cck_signup][download][revision] = "2fe932c" ; Test that revision passed in main level works as shorthand for download revision. projects[context_admin][revision] = "eb9f05e" ; When branch is passed in addition to revision, .info file rewriting has better versioning. projects[caption_filter][subdir] = "contrib" projects[caption_filter][download][type] = "git" projects[caption_filter][download][branch] = "7.x-1.x" projects[caption_filter][download][revision] = "c9794cf" drush-5.10.0/tests/makefiles/git.make000066400000000000000000000022251222105546100174450ustar00rootroot00000000000000core = 6.x api = 2 ; Test that a specific tag can be pulled. projects[tao][type] = theme projects[tao][download][type] = git projects[tao][download][tag] = 6.x-3.2 ; Test that a branch can be pulled. We use a super-old "stale" branch in the ; Drupalbin project that we expect not to change. projects[drupalbin][type] = profile projects[drupalbin][download][type] = git projects[drupalbin][download][branch] = 5.x-1.x ; Test that a specific revision can be pulled. Note that provision is not ; actually a module. projects[visitor][type] = module projects[visitor][download][type] = git projects[visitor][download][revision] = 5f256032cd4bcc2db45c962306d12c85131388ef ; Test a non-Drupal.org repository. projects[os_lightbox][type] = "module" projects[os_lightbox][download][type] = "git" projects[os_lightbox][download][url] = "https://github.com/opensourcery/os_lightbox.git" projects[os_lightbox][download][revision] = "8d60090f2" ; Test a refspec fetch. projects[storypal][type] = module projects[storypal][download][type] = git projects[storypal][download][url] = http://git.drupal.org/project/storypal.git projects[storypal][download][refspec] = refs/tags/7.x-1.0 drush-5.10.0/tests/makefiles/gzip.make000066400000000000000000000016461222105546100176410ustar00rootroot00000000000000api = 2 core = 6.x ; GeSHi-1.0.8.10.tar.gz contains wrapper folder "geshi/". ; This should move that wrapper folder to sites/all/libraries/geshi/ . libraries[geshi][destination] = libraries libraries[geshi][download][type] = get libraries[geshi][download][url] = http://downloads.sourceforge.net/project/geshi/geshi/GeSHi%201.0.8.10/GeSHi-1.0.8.10.tar.gz ; getid3 doesn't contain a wrapper folder. All files are in the root of the archive. libraries[getid3][destination] = libraries libraries[getid3][download][type] = get libraries[getid3][download][url] = "http://downloads.sourceforge.net/project/getid3/getID3%28%29%201.x/1.9.1/getid3-1.9.1-20110810.zip?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fgetid3%2Ffiles%2FgetID3%2528%2529%25201.x%2F1.9.1%2F&ts=1320871534" libraries[getid3][directory_name] = getid3 ; http://drupal.org/node/1336886 libraries[getid3][patch][] = http://drupal.org/files/getid3-remove-demos-1.9.1.patch drush-5.10.0/tests/makefiles/include.make000066400000000000000000000003201222105546100202770ustar00rootroot00000000000000core = 6.x api = 2 projects[drupal] = 6.17 projects[eldorado_superfly][version] = 1.1 includes[] = included.make ; Final build should contain: ; - drupal 6.17 ; - token ; - cck 2.6 ; - eldorado_superfly 1.1drush-5.10.0/tests/makefiles/included.make000066400000000000000000000001331222105546100204450ustar00rootroot00000000000000core = 6.x api = 2 projects[cck] = 2.6 projects[token] = FALSE includes[] = included2.makedrush-5.10.0/tests/makefiles/included2.make000066400000000000000000000001431222105546100205300ustar00rootroot00000000000000core = 6.x api = 2 projects[views] = 2.11 projects[token] = 1.13 projects[eldorado_superfly] = 1.0drush-5.10.0/tests/makefiles/limited-projects-libraries.make000066400000000000000000000007741222105546100241210ustar00rootroot00000000000000core = "7.x" api = 2 projects[boxes][version] = "1.0-beta7" projects[admin_menu][version] = "3.0-rc1" ; Use drupal.org as a nice stable source of libraries. libraries[drush_make][download][type] = "file" libraries[drush_make][download][url] = "http://ftp.drupal.org/files/projects/drush_make-6.x-2.3.tar.gz" ; Use drupal.org as a nice stable source of libraries. libraries[token][download][type] = "file" libraries[token][download][url] = "http://ftp.drupal.org/files/projects/token-7.x-1.0-rc1.tar.gz" drush-5.10.0/tests/makefiles/md5-fail.make000066400000000000000000000004431222105546100202600ustar00rootroot00000000000000core = 6.x api = 2 libraries[drush_make][download][type] = "file" libraries[drush_make][download][url] = "http://drupalcode.org/project/drush_make.git/blob_plain/8d4e770:/README.txt" libraries[drush_make][download][filename] = "README.txt" libraries[drush_make][download][md5] = "- fail -" drush-5.10.0/tests/makefiles/md5-succeed.make000066400000000000000000000004731222105546100207630ustar00rootroot00000000000000core = 6.x api = 2 libraries[drush_make][download][type] = "file" libraries[drush_make][download][url] = "http://drupalcode.org/project/drush_make.git/blob_plain/8d4e770:/README.txt" libraries[drush_make][download][filename] = "README.txt" libraries[drush_make][download][md5] = "c8968d801a953b9ea735364d6f3dfabc" drush-5.10.0/tests/makefiles/patches.make000066400000000000000000000013601222105546100203100ustar00rootroot00000000000000core = 7.x api = 2 ; Test that patches work projects[wysiwyg][version] = "2.1" ; http://drupal.org/node/624018#comment-5098162 projects[wysiwyg][patch][] = "http://drupal.org/files/0001-feature.inc-from-624018-211.patch" ; http://drupal.org/node/1152908#comment-5010536 projects[features][version] = "1.0-beta4" projects[features][patch][] = "http://drupal.org/files/issues/features-drush-backend-invoke-25.patch" projects[context][version] = "3.0-beta2" ; http://drupal.org/node/1251406#comment-5020012 projects[context][patch][] = "http://drupal.org/files/issues/custom_blocks_arent_editable-make.patch" ; http://drupal.org/node/661094#comment-4735064 projects[context][patch][] = "http://drupal.org/files/issues/661094-context-permissions.patch" drush-5.10.0/tests/makefiles/qd-devel.make000066400000000000000000000001701222105546100203600ustar00rootroot00000000000000core = "7.x" api = 2 projects[drupal][version] = "7.15" defaults[projects][subdir] = "contrib" projects[devel] = "1.3" drush-5.10.0/tests/makefiles/recursion.make000066400000000000000000000002111222105546100206640ustar00rootroot00000000000000core = 7.x api = 2 projects[drupal_forum][type] = "profile" projects[drupal_forum][version] = "1.0-alpha2" projects[views] = "3.0-rc3" drush-5.10.0/tests/makefiles/subtree.make000066400000000000000000000016471222105546100203420ustar00rootroot00000000000000core = 6.x api = 2 ; nivo-slider2.7.1.zip contains Mac OS X metadata (a "__MACOSX/" folder) in addition to the desired content in the "nivo-slider/" folder. ; Using the "subtree" directive, we tell Drush Make we only want the "nivo-slider/" folder. libraries[nivo-slider][download][type] = get libraries[nivo-slider][download][url] = https://github.com/downloads/gilbitron/Nivo-Slider/nivo-slider2.7.1.zip libraries[nivo-slider][download][sha1] = bd8e14b82f5b9c6f533a4e1aa26a790cd66c3cb9 libraries[nivo-slider][download][subtree] = nivo-slider ; Tell Drush Make we only want the "fullcalendar-1.5.3/fullcalendar/" folder. libraries[fullcalendar][download][type] = get libraries[fullcalendar][download][url] = http://arshaw.com/fullcalendar/downloads/fullcalendar-1.5.3.zip libraries[fullcalendar][download][sha1] = c7219b1ddd2b11ccdbf83ebd116872affbc45d7a libraries[fullcalendar][download][subtree] = fullcalendar-1.5.3/fullcalendar drush-5.10.0/tests/makefiles/svn.make000066400000000000000000000003201222105546100174620ustar00rootroot00000000000000core = 6.x api = 2 ; Test an SVN repo with revision flag. libraries[flot][download][type] = svn libraries[flot][download][url] = http://flot.googlecode.com/svn/trunk libraries[flot][download][revision] = 10 drush-5.10.0/tests/makefiles/translations-inside.make000066400000000000000000000002061222105546100226510ustar00rootroot00000000000000core = 6.x api = 2 translations[] = es translations[] = pt-br projects[drupal][version] = "6.22" projects[token][version] = "1.18" drush-5.10.0/tests/makefiles/translations-inside7.make000066400000000000000000000002131222105546100227360ustar00rootroot00000000000000core = 7.x api = 2 translations[] = es translations[] = pt-br projects[drupal][version] = "7.10" projects[token][version] = "1.0-beta7" drush-5.10.0/tests/makefiles/translations.make000066400000000000000000000001551222105546100214030ustar00rootroot00000000000000core = 6.x api = 2 projects[token][version] = "1.18" ; @TODO: Add a project that uses a custom l10n_server drush-5.10.0/tests/makefiles/use-distribution-as-core.make000066400000000000000000000003071222105546100235210ustar00rootroot00000000000000api = 2 core = 7.x projects[commerce_kickstart][type] = core projects[commerce_kickstart][version] = "7.x-1.19" projects[oauth2_server][type] = module projects[oauth2_server][version] = "1.0-beta2" drush-5.10.0/tests/phpunit.xml.dist000066400000000000000000000026531222105546100172430ustar00rootroot00000000000000 . . drush-5.10.0/tests/pmDownloadTest.php000066400000000000000000000102111222105546100175320ustar00rootroot00000000000000drush('pm-download', array('devel'), array('cache' => NULL, 'skip' => NULL)); // No FirePHP $this->assertFileExists(UNISH_SANDBOX . '/devel/README.txt'); } // @todo Test pure drush commandfile projects. They get special destination. public function testDestination() { // Setup two Drupal sites. Skip install for speed. $sites = $this->setUpDrupal(2, FALSE); $uri = key($sites); $root = $this->webroot(); // Common options for the invocations below. $devel_options = array( 'cache' => NULL, 'skip' => NULL, // No FirePHP 'invoke' => NULL, // Invoke from script: do not verify options ); // Default to sites/all. $options = array( 'root' => $root, 'uri' => $uri, ) + $devel_options; $this->drush('pm-download', array('devel'), $options); $this->assertFileExists($root . '/sites/all/modules/devel/README.txt'); // --use-site-dir // Expand above $options. $options += array('use-site-dir' => NULL); $this->drush('pm-download', array('devel'), $options); $this->assertFileExists("$root/sites/$uri/modules/devel/README.txt"); unish_file_delete_recursive("$root/sites/$uri/modules/devel"); // If we are in site specific dir, then download belongs there. $path_stage = "$root/sites/$uri"; // gets created by --use-site-dir above, // mkdir("$path_stage/modules"); $options = $devel_options; $this->drush('pm-download', array('devel'), $options, NULL, $path_stage); $this->assertFileExists($path_stage . '/modules/devel/README.txt'); // --destination with absolute path. $destination = UNISH_SANDBOX . '/test-destination1'; mkdir($destination); $options = array( 'destination' => $destination, ) + $devel_options; $this->drush('pm-download', array('devel'), $options); $this->assertFileExists($destination . '/devel/README.txt'); // --destination with a relative path. $destination = 'test-destination2'; mkdir(UNISH_SANDBOX . '/' . $destination); $options = array( 'destination' => $destination, ) + $devel_options; $this->drush('pm-download', array('devel'), $options); $this->assertFileExists(UNISH_SANDBOX . '/' . $destination . '/devel/README.txt'); } public function testSelect() { $options = array( 'cache' => NULL, 'no' => NULL, 'select' => NULL, ); // --select. Specify 6.x since that has so many releases. $this->drush('pm-download', array('devel-6.x'), $options); $items = $this->getOutputAsList(); $output = $this->getOutput(); // The maximums below are higher then they usually appear since --verbose can add one. $this->assertLessThanOrEqual(8, count($items), '--select offerred no more than 3 options.'); $this->assertContains('dev', $output, 'Dev release was shown by --select.'); // --select --all. Specify 6.x since that has so many releases. $this->drush('pm-download', array('devel-6.x'), $options + array('all' => NULL)); $items = $this->getOutputAsList(); $output = $this->getOutput(); $this->assertGreaterThanOrEqual(20, count($items), '--select --all offerred at least 16 options.'); $this->assertContains('6.x-1.5', $output, 'Assure that --all lists very old releases.'); // --select --dev. Specify 6.x since that has so many releases. $this->drush('pm-download', array('devel-6.x'), $options + array('dev' => NULL)); $items = $this->getOutputAsList(); $output = $this->getOutput(); $this->assertLessThanOrEqual(6, count($items), '--select --dev expected to offer only one option.'); $this->assertContains('6.x-1.x-dev', $output, 'Assure that --dev lists the only dev release.'); } public function testPackageHandler() { $options = array( 'cache' => NULL, 'package-handler' => 'git_drupalorg', 'yes' => NULL, ); $this->drush('pm-download', array('devel'), $options); $this->assertFileExists(UNISH_SANDBOX . '/devel/README.txt'); $this->assertFileExists(UNISH_SANDBOX . '/devel/.git'); } } drush-5.10.0/tests/pmEnDisUnListTest.php000066400000000000000000000060211222105546100201300ustar00rootroot00000000000000setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'pipe' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), 'cache' => NULL, 'skip' => NULL, // No FirePHP 'invoke' => NULL, // Don't validate options ); $this->drush('pm-download', array('devel'), $options); $this->drush('pm-list', array(), $options + array('no-core' => NULL, 'status' => 'not installed')); $list = $this->getOutputAsList(); $this->assertTrue(in_array('devel', $list)); $this->drush('pm-enable', array('devel'), $options); $this->drush('pm-list', array(), $options + array('status' => 'enabled')); $list = $this->getOutputAsList(); $this->assertTrue(in_array('devel', $list)); $this->assertTrue(in_array('bartik', $list), 'Themes are in the pm-list'); $this->drush('sql-query', array("SELECT path FROM menu_router WHERE path = 'devel/settings';"), array('root' => $this->webroot(), 'uri' => key($sites))); $list = $this->getOutputAsList(); $this->assertTrue(in_array('devel/settings', $list), 'Cache was cleared after modules were enabled'); $this->drush('pm-list', array(), $options + array('package' => 'Core')); $list = $this->getOutputAsList(); $this->assertFalse(in_array('devel', $list), 'Devel is not part of core package'); // For testing uninstall later. $this->drush('variable-set', array('devel_query_display', 1), $options); $this->drush('pm-disable', array('devel'), $options); $this->drush('pm-list', array(), $options + array('status' => 'disabled')); $list = $this->getOutputAsList(); $this->assertTrue(in_array('devel', $list)); $this->drush('pm-uninstall', array('devel'), $options); $this->drush('pm-list', array(), $options + array('status' => 'not installed', 'type' => 'module')); $list = $this->getOutputAsList(); $this->assertTrue(in_array('devel', $list)); $this->drush('variable-get', array('devel_query_display'), $options, NULL, NULL, self::EXIT_ERROR); $output = $this->getOutput(); $this->assertEmpty($output, 'Devel variable was uninstalled.'); // Test pm-enable is able to download dependencies. $this->drush('pm-download', array('pathauto'), $options); $this->drush('pm-enable', array('pathauto'), $options + array('resolve-dependencies' => TRUE)); $this->drush('pm-list', array(), $options + array('status' => 'enabled')); $list = $this->getOutputAsList(); $this->assertTrue(in_array('token', $list)); // Test that pm-enable downloads missing projects and dependencies. $this->drush('pm-enable', array('views'), $options + array('resolve-dependencies' => TRUE)); $this->drush('pm-list', array(), $options + array('status' => 'enabled')); $list = $this->getOutputAsList(); $this->assertTrue(in_array('ctools', $list)); } } drush-5.10.0/tests/pmUpdateCodeTest.php000066400000000000000000000060211222105546100200040ustar00rootroot00000000000000setUpDrupal(1, TRUE, '7.0-rc3'); $options = array( 'root' => $this->webroot(), 'uri' => key($sites), 'yes' => NULL, 'quiet' => NULL, 'cache' => NULL, 'skip' => NULL, // No FirePHP 'invoke' => NULL, // invoke from script: do not verify options ); $this->drush('pm-download', array('devel-7.x-1.0-rc1,webform-7.x-3.4-beta1'), $options); $this->drush('pm-enable', array('menu', 'devel', 'webform'), $options); } function testUpdateCode() { $options = array( 'root' => $this->webroot(), 'uri' => key($this->sites), // Have to access class property since $sites in in setUp(). 'yes' => NULL, 'backup-dir' => UNISH_SANDBOX . '/backups', ); // Try to upgrade a specific module. $this->drush('pm-updatecode', array('devel'), $options + array()); // Assure that devel was upgraded and webform was not. $this->drush('pm-updatecode', array(), $options + array('pipe' => NULL)); $all = $this->getOutput(); $this->assertNotContains('devel', $all); $this->assertContains('webform', $all); // Lock webform, and update core. $this->drush('pm-updatecode', array(), $options + array('lock' => 'webform')); $list = $this->getOutputAsList(); // For debugging. $this->drush('pm-updatecode', array(), $options + array('pipe' => NULL)); $all = $this->getOutput(); $this->assertNotContains('drupal', $all, 'Core was updated'); $this->assertContains('webform', $all, 'Webform was skipped.'); // Unlock webform, update, and check. $this->drush('pm-updatecode', array(), $options + array('unlock' => 'webform', 'no-backup' => NULL)); $list = $this->getOutputAsList(); $this->drush('pm-updatecode', array(), $options + array('pipe' => NULL)); $all = $this->getOutput(); $this->assertNotContains('webform', $all, 'Webform was updated'); // Verify that we keep backups as instructed. $backup_dir = UNISH_SANDBOX . '/backups'; $Directory = new RecursiveDirectoryIterator($backup_dir); $Iterator = new RecursiveIteratorIterator($Directory); $found = FALSE; foreach ($Iterator as $item) { if (basename($item) == 'devel.module') { $found = TRUE; break; } } $this->assertTrue($found, 'Backup exists and contains devel module.'); $Iterator = new RecursiveIteratorIterator($Directory); $found = FALSE; foreach ($Iterator as $item) { if (basename($item) == 'webform.module') { $found = TRUE; break; } } $this->assertFalse($found, 'Backup exists and does not contain webformmodule.'); } } drush-5.10.0/tests/queueTest.php000066400000000000000000000024701222105546100165620ustar00rootroot00000000000000setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), ); // Enable aggregator since it declares a queue. $this->drush('en', array('aggregator'), $options); $this->drush('queue-list', array(), $options); $output = $this->getOutput(); $this->assertContains('aggregator_feeds', $output, 'Queue list shows the declared queue.'); $this->drush('php-script', array('queue_script'), $options + array('script-path' => dirname(__FILE__) . '/resources')); $this->drush('queue-list', array(), $options + array('pipe' => TRUE)); $output = trim($this->getOutput()); $parts = explode(",", $output); $this->assertEquals('aggregator_feeds,1,SystemQueue', $output, 'Item was successfully added to the queue.'); $output = $this->getOutput(); $this->drush('queue-run', array('aggregator_feeds'), $options); $this->drush('queue-list', array(), $options + array('pipe' => TRUE)); $output = trim($this->getOutput()); $parts = explode(",", $output); $this->assertEquals('aggregator_feeds,0,SystemQueue', $output, 'Queue item processed.'); } } drush-5.10.0/tests/quickDrupalTest.php000066400000000000000000000037701222105546100177260ustar00rootroot00000000000000makefile_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'makefiles'; } /** * Run a given quick-drupal test. * * @param $test * The test makefile to run, as defined by $this->getQuickDrupalTestParameters(); */ private function runQuickDrupalTest($test) { $config = $this->getQuickDrupalTestParameters($test); $default_options = array( 'yes' => NULL, 'no-server' => NULL, ); $options = array_merge($config['options'], $default_options); if (array_key_exists('makefile', $config)) { $makefile = $this->makefile_path . DIRECTORY_SEPARATOR . $config['makefile']; $options['makefile'] = $makefile; } $return = !empty($config['fail']) ? self::EXIT_ERROR : self::EXIT_SUCCESS; $target = UNISH_SANDBOX . '/qd-' . $test; $options['root'] = $target; $this->drush('core-quick-drupal', $config['args'], $options, NULL, NULL, $return); // Use pm-list to determine if all of the correct modules were enabled if (empty($config['fail'])) { $this->drush('pm-list', array(), array('root' => $target, 'status' => 'enabled', 'no-core' => NULL, 'pipe' => NULL)); $output = $this->getOutput(); $this->assertEquals($config['expected-modules'], $output, 'quick-drupal included the correct set of modules'); } } function testQuickDrupal() { $this->runQuickDrupalTest('devel'); } function getQuickDrupalTestParameters($key) { static $tests = array( 'devel' => array( 'name' => 'Test quick-drupal with a makefile that downloads devel', 'makefile' => 'qd-devel.make', 'expected-modules' => 'devel', 'args' => array(), 'options' => array(), ), ); return $tests[$key]; } } drush-5.10.0/tests/releaseInfoTest.php000066400000000000000000000056761222105546100177050ustar00rootroot00000000000000assertArrayHasKey('devel', $request_data); $this->assertEquals($request_data['devel']['drupal_version'], '6.x'); $this->assertEquals($request_data['devel']['project_version'], '1.18'); } /* * Pick right release from the XML (dev, latest published+recommended, ...). */ public function testReleaseXML() { _drush_add_commandfiles(array(DRUSH_BASE_PATH . '/commands/pm')); drush_include_engine('release_info', 'updatexml'); // Use a local, static XML file because live files change over time. $xml = simplexml_load_file(dirname(__FILE__). '/devel.xml'); // Pick specific release. $request_data = array( 'name' => 'devel', 'drupal_version' => '6.x', 'project_version' => '1.18', 'version' => '6.x-1.18', ); $release = updatexml_parse_release($request_data, $xml); $this->assertEquals($release['version'], '6.x-1.18'); // Pick latest recommended+published with no further specification. // 6.x-2.2 is skipped because it is unpublished. // 6.x-2.2-rc1 is skipped because it is not a stable release. $request_data = array( 'name' => 'devel', 'drupal_version' => '6.x', ); $release = updatexml_parse_release($request_data, $xml); $this->assertEquals($release['version'], '6.x-2.1'); // Pick latest from a specific branch. $request_data = array( 'name' => 'devel', 'drupal_version' => '6.x', 'version' => '6.x-1', ); $release = updatexml_parse_release($request_data, $xml); $this->assertEquals($release['version'], '6.x-1.23'); // Pick latest from a different branch. $request_data = array( 'name' => 'devel', 'drupal_version' => '6.x', 'version' => '6.x-2', ); $release = updatexml_parse_release($request_data, $xml); // 6.x-2.2 is skipped because it is unpublished. // 6.x-2.2-rc1 is skipped because it is not a stable release. $this->assertEquals($release['version'], '6.x-2.1'); // Pick a -dev release. $request_data = array( 'name' => 'devel', 'drupal_version' => '6.x', 'version' => '6.x-1.x', ); $release = updatexml_parse_release($request_data, $xml); $this->assertEquals($release['version'], '6.x-1.x-dev'); // Test $restrict_to parameter. $request_data['version'] = '6.x-1'; $release = updatexml_parse_release($request_data, $xml, 'version'); $this->assertEquals($release['version'], '6.x-1.23'); $release = updatexml_parse_release($request_data, $xml, 'dev'); $this->assertEquals($release['version'], '6.x-1.x-dev'); } } drush-5.10.0/tests/resources/000077500000000000000000000000001222105546100160745ustar00rootroot00000000000000drush-5.10.0/tests/resources/create_node_types.php000066400000000000000000000013111222105546100222750ustar00rootroot00000000000000 'page', 'name' => 'Basic page', 'base' => 'node_content', 'description' => 'Use basic pages for your static content, such as an \'About us\' page.', 'custom' => 1, 'modified' => 1, 'locked' => 0, ), array( 'type' => 'article', 'name' => 'Article', 'base' => 'node_content', 'description' => 'Use articles for time-sensitive content like news, press releases or blog posts.', 'custom' => 1, 'modified' => 1, 'locked' => 0, ), ); foreach ($types as $type) { $type = node_type_set_defaults($type); node_type_save($type); node_add_body_field($type); } drush-5.10.0/tests/resources/example.profile000066400000000000000000000022741222105546100211160ustar00rootroot00000000000000 'Example', 'description' => 'Example profile with a couple of basic added configuration options.', ); } function example_profile_modules() { return array(); } function example_form_alter(&$form, $form_state, $form_id) { if ($form_id == 'install_configure') { $form['my_options'] = array( '#type' => 'fieldset', '#title' => t('Example options'), ); $form['my_options']['myopt1'] = array( '#type' => 'textfield', '#title' => 'Example option 1' ); $form['my_options']['myopt2'] = array( '#type' => 'select', '#title' => t('Example option 2'), '#options' => array( 0 => t('Something'), 1 => t('Something else'), 2 => t('Something completely different'), ), ); // Make sure we don't clobber the original auto-detected submit func $form['#submit'] = array('install_configure_form_submit', 'example_install_configure_form_submit'); } } function example_install_configure_form_submit($form, &$form_state) { variable_set('myopt1', $form_state['values']['myopt1']); variable_set('myopt2', $form_state['values']['myopt2']); }drush-5.10.0/tests/resources/queue_script.php000066400000000000000000000004251222105546100213160ustar00rootroot00000000000000 'test', 'url' => 'http://drupal.org/project/issues/rss/drupal?categories=All', 'refresh' => 3600, 'block' => 5, ); // Create a new feed. aggregator_save_feed($feed); // Let cron call DrupalQueue::createItem() for us. aggregator_cron(); drush-5.10.0/tests/runner.php000077500000000000000000000011751222105546100161130ustar00rootroot00000000000000#!/usr/bin/env php addFileToBlacklist(__FILE__, 'PHPUNIT'); if (extension_loaded('xdebug')) { // Drush comments out the following line for easier debugging. // xdebug_disable(); } if (strpos('/usr/bin/php', '@php_bin') === 0) { set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path()); } require_once 'PHPUnit/Autoload.php'; define('PHPUnit_MAIN_METHOD', 'PHPUnit_TextUI_Command::main'); PHPUnit_TextUI_Command::main(); drush-5.10.0/tests/shellAliasTest.php000066400000000000000000000133771222105546100175270ustar00rootroot00000000000000 'topic core-global-options', 'pull' => '!git pull', 'echosimple' => '!echo {{@target}}', 'echotest' => '!echo {{@target}} {{%root}} {{%mypath}}', 'compound-command' => '!cd {{%sandbox}} && echo second', ); "; file_put_contents(UNISH_SANDBOX . '/drushrc.php', trim($contents)); mkdir(UNISH_SANDBOX . '/b'); $contents = " '!echo alternate config file included too', ); "; file_put_contents(UNISH_SANDBOX . '/b/drushrc.php', trim($contents)); $contents = " '/path/to/drupal', 'uri' => 'mysite.org', '#peer' => '@live', 'path-aliases' => array ( '%mypath' => '/srv/data/mypath', '%sandbox' => '" . UNISH_SANDBOX . "' ), ); "; file_put_contents(UNISH_SANDBOX . '/aliases.drushrc.php', trim($contents)); } /* * Test shell aliases to Drush commands. */ public function testShellAliasDrushLocal() { $options = array( 'config' => UNISH_SANDBOX, ); $this->drush('glopts', array(), $options); $output = $this->getOutput(); $this->assertContains('These options are applicable to most drush commands.', $output, 'Successfully executed local shell alias to drush command'); } /* * Test shell aliases to Bash commands. Assure we pass along extra arguments * and options. */ public function testShellAliasBashLocal() { $options = array( 'config' => UNISH_SANDBOX, 'simulate' => NULL, 'rebase' => NULL, ); $this->drush('pull', array('origin'), $options, NULL, NULL, self::EXIT_SUCCESS, '2>&1'); $output = $this->getOutput(); $this->assertContains('Calling proc_open(git pull origin --rebase);', $output); } public function testShellAliasDrushRemote() { $options = array( 'config' => UNISH_SANDBOX, 'simulate' => NULL, 'ssh-options' => '', ); $this->drush('glopts', array(), $options, 'user@server/path/to/drupal#sitename'); // $expected might be different on non unix platforms. We shall see. // n.b. --config is not included in calls to remote systems. $bash = $this->escapeshellarg('drush --invoke --simulate --nocolor --uri=sitename --root=/path/to/drupal core-topic core-global-options 2>&1'); $expected = "Simulating backend invoke: ssh user@server $bash 2>&1"; $output = $this->getOutput(); $this->assertEquals($expected, $output, 'Expected remote shell alias to a drush command was built'); } public function testShellAliasBashRemote() { $options = array( 'config' => UNISH_SANDBOX, 'simulate' => NULL, 'ssh-options' => '', 'rebase' => NULL, ); $this->drush('pull', array('origin'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); // $expected might be different on non unix platforms. We shall see. $expected = "Calling proc_open(ssh user@server 'cd /path/to/drupal && git pull origin --rebase');"; $output = $this->getOutput(); $this->assertEquals($expected, $output, 'Expected remote shell alias to a bash command was built'); } /* * Test shell aliases with simple replacements -- no alias. */ public function testShellAliasSimpleReplacement() { $options = array( 'config' => UNISH_SANDBOX, ); $this->drush('echosimple', array(), $options); // Windows command shell actually prints quotes. See http://drupal.org/node/1452944. $expected = $this->is_windows() ? '"@none"' : '@none'; $output = $this->getOutput(); $this->assertEquals($expected, $output); } /* * Test shell aliases with complex replacements -- no alias. */ public function testShellAliasReplacementNoAlias() { $options = array( 'config' => UNISH_SANDBOX, ); // echo test has replacements that are not satisfied, so this is expected to return an error. $this->drush('echotest', array(), $options, NULL, NULL, self::EXIT_ERROR); } /* * Test shell aliases with replacements -- alias. */ public function testShellAliasReplacementWithAlias() { $options = array( 'config' => UNISH_SANDBOX, 'alias-path' => UNISH_SANDBOX, ); $this->drush('echotest', array(), $options, '@myalias'); // Windows command shell actually prints quotes. See http://drupal.org/node/1452944. $expected = $this->is_windows() ? '"@myalias"' : '@myalias'; $expected .= ' /path/to/drupal /srv/data/mypath'; $output = $this->getOutput(); $this->assertEquals($expected, $output); } /* * Test shell aliases with replacements and compound commands. */ public function testShellAliasCompoundCommands() { $options = array( 'config' => UNISH_SANDBOX, 'alias-path' => UNISH_SANDBOX, ); $this->drush('compound-command', array(), $options, '@myalias'); $expected = 'second'; $output = $this->getOutput(); $this->assertEquals($expected, $output); } /* * Test shell aliases with multiple config files. */ public function testShellAliasMultipleConfigFiles() { $options = array( 'config' => UNISH_SANDBOX . "/b" . PATH_SEPARATOR . UNISH_SANDBOX, 'alias-path' => UNISH_SANDBOX, ); $this->drush('also', array(), $options); $expected = "alternate config file included too"; $output = $this->getOutput(); $this->assertEquals($expected, $output); } } drush-5.10.0/tests/siteAliasTest.php000066400000000000000000000054731222105546100173620ustar00rootroot00000000000000 'fake.remote-host.com', 'remote-user' => 'www-admin', 'root' => '/fake/path/to/root', 'uri' => 'default', 'site' => 'stage', 'command-specific' => array( 'rsync' => array( 'delete' => TRUE, ), ), ); EOD; file_put_contents($aliasFile, $aliasContents); $options = array( 'alias-path' => $aliasPath, 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile. 'simulate' => TRUE, ); $this->drush('core-rsync', array('/a', '/b'), $options, '@test'); $output = $this->getOutput(); $command_position = strpos($output, 'core-rsync'); $special_option_position = strpos($output, '--site=stage'); $command_specific_position = strpos($output, '--delete'); $this->assertTrue($command_position !== FALSE); $this->assertTrue($special_option_position !== FALSE); $this->assertTrue($command_specific_position !== FALSE); $this->assertTrue($command_position > $special_option_position); $this->assertTrue($command_position < $command_specific_position); } /* * Assure that site lists work as expected. * @todo Use --backend for structured return data. Depends on http://drupal.org/node/1043922 */ public function testSAList() { $sites = $this->setUpDrupal(2); $subdirs = array_keys($sites); $eval = 'print "bon";'; $options = array( 'yes' => NULL, 'verbose' => NULL, 'root' => $this->webroot(), ); foreach ($subdirs as $dir) { $dirs[] = "#$dir"; } $this->drush('php-eval', array($eval), $options, implode(',', $dirs)); $output = $this->getOutputAsList(); $expected = "You are about to execute 'php-eval print \"bon\";' non-interactively (--yes forced) on all of the following targets: #dev #stage Continue? (y/n): y #stage >> bon #dev >> bon"; $this->assertEquals($expected, implode("\n", $output)); } } drush-5.10.0/tests/siteAliasUnitTest.php000066400000000000000000000026741222105546100202220ustar00rootroot00000000000000 'fake.remote-host.com', 'remote-user' => 'www-admin', 'root' => '/fake/path/to/root', 'uri' => 'default', 'command-specific' => array( 'rsync' => array( 'delete' => TRUE, ), ), ); // Site alias which overrides some settings from $site_alias_a. $site_alias_b = array( 'remote-host' => 'another-fake.remote-host.com', 'remote-user' => 'www-other', 'root' => '/fake/path/to/root', 'uri' => 'default', 'command-specific' => array( 'rsync' => array( 'delete' => FALSE, ), ), ); // Expected result from merging $site_alias_a and $site_alias_b. $site_alias_expected = array( 'remote-host' => 'another-fake.remote-host.com', 'remote-user' => 'www-other', 'root' => '/fake/path/to/root', 'uri' => 'default', 'command-specific' => array( 'rsync' => array( 'delete' => FALSE, ), ), ); $site_alias_result = _sitealias_array_merge($site_alias_a, $site_alias_b); $this->assertEquals($site_alias_expected, $site_alias_result); } } drush-5.10.0/tests/siteIntallD6Test.php000066400000000000000000000041521222105546100177370ustar00rootroot00000000000000markTestSkipped('This test class is designed for Drupal 6.'); return; } } /* * Test a D6 install with extra options. */ public function testExtraConfigurationOptions() { // Set up codebase without installing Drupal. $sites = $this->setUpDrupal(1, FALSE, '6'); $root = $this->webroot(); $site = key($sites); // Copy the "example" test profile into the newly created site's profiles directory $profile_dir = "$root/profiles/example"; mkdir($profile_dir); copy(dirname(__FILE__) . '/resources/example.profile', $profile_dir . '/example.profile'); $test_string = $this->randomString(); // example.profile Has values 0-2 defined as allowed. $test_int = rand(0, 2); $site_name = $this->randomString(); $this->drush('site-install', array( // First argument is the profile name 'example', // Then the extra profile options "myopt1=$test_string", "myopt2=$test_int", ), array( 'db-url' => $this->db_url($site), 'yes' => NULL, 'sites-subdir' => $site, 'root' => $root, 'site-name' => $site_name, 'uri' => $site, )); $this->checkVariable('site_name', $site_name, $site); $this->checkVariable('myopt1', $test_string, $site); $this->checkVariable('myopt2', $test_int, $site); } /** * Check the value of a Drupal variable against an expectation using drush. * * @param $name * The variable name. * @param $value * The expected value of this variable. * @param $site * The name of an individual multisite installation site. */ private function checkVariable($name, $value, $site) { $options = array( 'root' => $this->webroot(), 'uri' => $site, ); $this->drush('vget', array($name), $options); $this->assertEquals($name . ': "' . $value . '"', $this->getOutput()); } } drush-5.10.0/tests/siteSetTest.php000066400000000000000000000025151222105546100170560ustar00rootroot00000000000000is_windows()) { $this->markTestSkipped('Site-set not currently available on Windows.'); } $sites = $this->setUpDrupal(1, TRUE); $site_names = array_keys($sites); $alias = '@' . $site_names[0]; $this->drush('ev', array("drush_invoke('site-set', '$alias'); print drush_sitealias_site_get();")); $output = $this->getOutput(); $this->assertEquals("Site set to $alias\n$alias", $output); $this->drush('site-set', array($alias)); $expected = 'Site set to ' . $alias; $output = $this->getOutput(); $this->assertEquals($expected, $output); $this->drush('site-set', array()); $output = $this->getOutput(); $this->assertEquals('Site set to @none', $output); $this->drush('ev', array("drush_invoke('site-set', '$alias'); print drush_sitealias_site_get();")); $output = $this->getOutput(); $this->assertEquals("Site set to $alias $alias", $output); $this->drush('ev', array("drush_invoke('site-set', '$alias'); drush_invoke('site-set', '@none'); drush_invoke('site-set', '-'); print drush_sitealias_site_get();")); $output = $this->getOutput(); $this->assertEquals("Site set to $alias Site set to @none Site set to $alias $alias", $output); } } drush-5.10.0/tests/siteSetUnitTest.php000066400000000000000000000007771222105546100177260ustar00rootroot00000000000000is_windows()) { $this->markTestSkipped('Site-set not currently available on Windows.'); } $tmp_path = UNISH_TMP; putenv("TMPDIR=$tmp_path"); $posix_pid = posix_getppid(); $expected_file = UNISH_TMP . '/drush-env/drush-drupal-site-' . $posix_pid; $filename = drush_sitealias_get_envar_filename(); $this->assertEquals($expected_file, $filename); } } drush-5.10.0/tests/siteSshTest.php000066400000000000000000000047761222105546100170730ustar00rootroot00000000000000is_windows()) { $this->markTestSkipped('ssh command not currently available on Windows.'); } $options = array( 'simulate' => NULL, ); $this->drush('ssh', array(), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); $output = $this->getOutput(); $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s);', self::escapeshellarg('user'), self::escapeshellarg('server')); $this->assertEquals($expected, $output); } /* * Test drush ssh --simulate 'date'. * @todo Run over a site list. drush_sitealias_get_record() currently cannot * handle a site list comprised of longhand site specifications. */ public function testNonInteractive() { $options = array( 'simulate' => NULL, ); $this->drush('ssh', array('date'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); $output = $this->getOutput(); $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s %s);', self::escapeshellarg('user'), self::escapeshellarg('server'), self::escapeshellarg('date')); $this->assertEquals($expected, $output); } /** * Test drush ssh with multiple arguments (preferred form). */ public function testSshMultipleArgs() { $options = array( 'simulate' => NULL, ); $this->drush('ssh', array('ls', '/path1', '/path2'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); $output = $this->getOutput(); $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s \'ls /path1 /path2\');', self::escapeshellarg('user'), self::escapeshellarg('server')); $this->assertEquals($expected, $output); } /** * Test drush ssh with multiple arguments (legacy form). */ public function testSshMultipleArgsLegacy() { $options = array( 'simulate' => NULL, ); $this->drush('ssh', array('ls /path1 /path2'), $options, 'user@server/path/to/drupal#sitename', NULL, self::EXIT_SUCCESS, '2>&1'); $output = $this->getOutput(); $expected = sprintf('Calling proc_open(ssh -o PasswordAuthentication=no %s@%s \'ls /path1 /path2\');', self::escapeshellarg('user'), self::escapeshellarg('server')); $this->assertEquals($expected, $output); } } drush-5.10.0/tests/sqlConnectTest.php000066400000000000000000000021541222105546100175460ustar00rootroot00000000000000setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), ); // @todo: extend to other DB platforms. if (strpos(UNISH_DB_URL, 'mysql') === FALSE) { $this->markTestSkipped('sql-connect tests need updating for non-mysql UNISH_DB_URL.'); } // Get the connection details with sql-connect and check its structure. $this->drush('sql-connect', array(), $options); $output = $this->getOutput(); $this->assertRegExp('/^mysql --database=[^\s]+ --host=[^\s]+ --user=[^\s]+ --password=.*$/', $output); // Issue a query and check the result to verify the connection. $this->execute($output . ' -e "select name from users where uid = 1;"'); $output = $this->getOutput(); $this->assertContains('admin', $output); } } drush-5.10.0/tests/sqlSyncTest.php000066400000000000000000000071231222105546100170720ustar00rootroot00000000000000markTestSkipped('SQL Sync does not apply to SQLite.'); return; } $sites = $this->setUpDrupal(2, TRUE); return $this->localSqlSync(); } /** * Do the same test as above, but use Drupal 6 sites instead of Drupal 7. */ public function testLocalSqlSyncD6() { if (UNISH_DRUPAL_MAJOR_VERSION != 6) { $this->markTestSkipped('This test class is designed for Drupal 6.'); return; } chdir(UNISH_TMP); // Avoids perm denied Windows error. $this->setUpBeforeClass(); $sites = $this->setUpDrupal(2, TRUE, '6'); return $this->localSqlSync(); } public function localSqlSync() { $dump_dir = UNISH_SANDBOX . "/dump-dir"; if (!is_dir($dump_dir)) { mkdir($dump_dir); } // Create a user in the staging site $name = 'joe.user'; $mail = "joe.user@myhome.com"; $options = array( 'root' => $this->webroot(), 'uri' => 'stage', 'yes' => NULL, ); $this->drush('user-create', array($name), $options + array('password' => 'password', 'mail' => $mail)); // Copy stage to dev with --sanitize $sync_options = array( 'sanitize' => NULL, 'yes' => NULL, 'dump-dir' => $dump_dir ); $this->drush('sql-sync', array('@stage', '@dev'), $sync_options); // Confirm that the sample user has the correct email address on the staging site $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $uid = $row[1]; $this->assertEquals($mail, $row[2], 'email address is unchanged on source site.'); $this->assertEquals($name, $row[0]); $options = array( 'root' => $this->webroot(), 'uri' => 'dev', 'yes' => NULL, ); // Confirm that the sample user's email address has been sanitized on the dev site $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $uid = $row[1]; $this->assertEquals("user+$uid@localhost", $row[2], 'email address was sanitized on destination site.'); $this->assertEquals($name, $row[0]); // Copy stage to dev with --sanitize and a fixed sanitized email $sync_options = array( 'sanitize' => NULL, 'yes' => NULL, 'dump-dir' => $dump_dir, 'sanitize-email' => 'user@localhost', ); $this->drush('sql-sync', array('@stage', '@dev'), $sync_options); $options = array( 'root' => $this->webroot(), 'uri' => 'dev', 'yes' => NULL, ); // Confirm that the sample user's email address has been sanitized on the dev site $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $uid = $row[1]; $this->assertEquals("user@localhost", $row[2], 'email address was sanitized (fixed email) on destination site.'); $this->assertEquals($name, $row[0]); } } drush-5.10.0/tests/unit.drush.inc000066400000000000000000000104431222105546100166620ustar00rootroot00000000000000 'No-op command, used to test completion for commands that start the same as other commands.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, ); $items['unit-eval'] = array( 'description' => 'Works like php-eval. Used for testing $command_specific context.', 'bootstrap' => DRUSH_BOOTSTRAP_MAX, 'callback' => 'drush_core_php_eval', // Note - no invoke hooks. ); $items['unit-invoke'] = array( 'description' => 'Return an array indicating which invoke hooks got called.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, ); $items['unit-batch'] = array( 'description' => 'Run a batch process.', 'bootstrap' => DRUSH_BOOTSTRAP_MAX, ); $items['unit-return-options'] = array( 'description' => 'Return options as function result.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, ); $items['unit-return-argv'] = array( 'description' => 'Return original argv as function result.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, ); $items['missing-callback'] = array( 'description' => 'Command with no callback function, to test error reporting.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, ); $items['unit-drush-dependency'] = array( 'description' => 'Command depending on an unknown commandfile.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, 'drush dependencies' => array('unknown-commandfile'), ); return $items; } // Implement each invoke hook with the same single line of code. // That line records that the hook was called. function drush_unit_invoke_init() {unit_invoke_log(__FUNCTION__);} function drush_unit_invoke_validate() {unit_invoke_log(__FUNCTION__);} function drush_unit_pre_unit_invoke() {unit_invoke_log(__FUNCTION__);} function drush_unit_invoke() {unit_invoke_log(__FUNCTION__);} function drush_unit_pre_unit_invoke_rollback() {unit_invoke_log(__FUNCTION__);} function drush_unit_post_unit_invoke_rollback() {unit_invoke_log(__FUNCTION__);} // Record that hook_drush_init() fired. function unit_drush_init() { $command = drush_get_command(); if ($command['command'] == 'unit-invoke') { unit_invoke_log(__FUNCTION__); } } function drush_unit_post_unit_invoke() { // Record that this hook was called. unit_invoke_log(__FUNCTION__); // Make sure we enter into rollback. drush_set_error(''); } /* * The final invoke hook. Emit the call history. * Cannot use 'exit' as it does not fire in rollback scenario. */ function drush_unit_invoke_validate_rollback() { unit_invoke_log(__FUNCTION__); print json_encode(unit_invoke_log()); } function unit_invoke_log($function = NULL) { static $called = array(); if ($function) { $called[] = $function; } else { return $called; } } /** * Command callback. */ function drush_unit_batch() { // Reduce php memory/time limits to test backend respawn. // TODO. $batch = array( 'operations' => array( array('_drush_unit_batch_operation', array()), ), 'finished' => '_drush_unit_batch_finished', // 'file' => Doesn't work for us. Drupal 7 enforces this path // to be relative to DRUPAL_ROOT. // @see _batch_process(). ); batch_set($batch); drush_backend_batch_process(); // Print the batch output. drush_backend_output(); } function _drush_unit_batch_operation(&$context) { $context['message'] = "!!! ArrayObject does its job."; for ($i = 0; $i < 5; $i++) { drush_print("Iteration $i"); } $context['finished'] = 1; } function _drush_unit_batch_finished() { // Restore php limits. // TODO. } // Return all of the option values passed in to this routine, minus the // global options. function drush_unit_return_options() { $all_option_values = array_merge(drush_get_context('cli'), drush_get_context('stdin')); foreach (drush_get_global_options() as $key => $info) { unset($all_option_values[$key]); } if (isset($all_option_values['log-message'])) { drush_log($all_option_values['log-message'], 'warning'); drush_log("done", 'warning'); unset($all_option_values['log-message']); } return $all_option_values; } // Return all of the original arguments passed to this script function drush_unit_return_argv() { return drush_get_context('argv'); } drush-5.10.0/tests/userTest.php000066400000000000000000000125651222105546100164220ustar00rootroot00000000000000setUpDrupal(1, TRUE); $root = $this->webroot(); $name = "example"; $options = array( 'root' => $root, 'uri' => key($sites), 'yes' => NULL, ); $this->drush('user-create', array($name), $options + array('password' => 'password', 'mail' => "example@example.com")); $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $uid = $row[1]; $this->assertEquals('example@example.com', $row[2]); $this->assertEquals($name, $row[0]); $this->assertEquals(1, $row[3], 'Newly created user is Active.'); $this->assertEquals('authenticated user', $row[4], 'Newly created user has one role.'); // user-block $this->drush('user-block', array($name), $options); $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $this->assertEquals(0, $row[3], 'User is blocked.'); // user-unblock $this->drush('user-unblock', array($name), $options); $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $this->assertEquals(1, $row[3], 'User is unblocked.'); // user-add-role // first, create the fole since we use testing install profile. $eval = "user_role_save((object)array('name' => 'administrator'))"; $this->drush('php-eval', array($eval), $options); $this->drush('user-add-role', array('administrator', $name), $options); $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $this->assertEquals('authenticated user,administrator', $row[4], 'User has administrator role.'); // user-remove-role $this->drush('user-remove-role', array('administrator', $name), $options); $this->drush('user-information', array($name), $options + array('pipe' => NULL)); $output = $this->getOutput(); $row = str_getcsv($output); $this->assertEquals('authenticated user', $row[4], 'User removed administrator role.'); // user-password $newpass = 'newpass'; $this->drush('user-password', array($name), $options + array('password' => $newpass)); $eval = "require_once DRUSH_DRUPAL_CORE . '/' . variable_get('password_inc', 'includes/password.inc');"; $eval .= "\$account = user_load_by_name('example');"; $eval .= "print (string) user_check_password('$newpass', \$account)"; $this->drush('php-eval', array($eval), $options); $output = $this->getOutput(); $this->assertEquals('1', $output, 'User can login with new password.'); // user-login $user_login_options = $options + array('simulate' => TRUE, 'browser' => 'unish'); // Collect full logs so we can check browser. $this->drush('user-login', array(), $user_login_options + array('backend' => NULL)); $parsed = parse_backend_output($this->getOutput()); $url = parse_url($parsed['output']); $this->assertStringStartsWith('/user/reset/1', $url['path'], 'Login returned a reset URL for uid 1 by default'); $browser = FALSE; foreach ($parsed['log'] as $key => $log) { if (strpos($log['message'], 'Opening browser unish at http://dev/user/reset/1') === 0) { $browser = TRUE; } } $this->assertEquals($browser, TRUE, 'Correct browser opened at correct URL'); // Check specific user and path argument. $this->drush('user-login', array($name, 'node/add'), $user_login_options); $output = $this->getOutput(); $url = parse_url($output); $this->assertStringStartsWith('/user/reset/' . $uid, $url['path'], 'Login with user argument returned a valid reset URL'); $this->assertEquals('destination=node/add', $url['query'], 'Login included destination path in URL'); // Check path used as only argument when using uid option. $this->drush('user-login', array('node/add'), $user_login_options + array('uid' => $uid)); $output = $this->getOutput(); $url = parse_url($output); $this->assertStringStartsWith('/user/reset/' . $uid, $url['path'], 'Login with uid option returned a valid reset URL'); $this->assertEquals('destination=node/add', $url['query'], 'Login included destination path in URL'); // user-cancel // create content $this->drush('php-script', array('create_node_types'), $options + array('script-path' => dirname(__FILE__) . '/resources')); $this->drush('php-eval', array($eval), $options); $eval = "\$node = (object) array('title' => 'foo', 'uid' => 2, 'type' => 'page',); node_save(\$node);"; $this->drush('php-eval', array($eval), $options); $this->drush('user-cancel', array($name), $options + array('delete-content' => NULL)); $eval = 'print (string) user_load(2)'; $this->drush('php-eval', array($eval), $options); $output = $this->getOutput(); $this->assertEmpty($output, 'User was deleted'); $eval = 'print (string) node_load(2)'; $this->drush('php-eval', array($eval), $options); $output = $this->getOutput(); $this->assertEmpty($output, 'Content was deleted'); } } drush-5.10.0/tests/variableTest.php000066400000000000000000000032411222105546100172200ustar00rootroot00000000000000setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'pipe' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), ); $this->drush('variable-set', array('date_default_timezone', 'US/Mountain'), $options); $this->drush('variable-get', array('date_default_timezone'), $options); // Wildcard get. $var_export = $this->getOutput(); eval($var_export); $this->assertEquals('US/Mountain', $variables['date_default_timezone'], 'Variable was successfully set and get.'); $this->drush('variable-set', array('site_name', 'control'), $options + array('exact' => NULL)); $this->drush('variable-set', array('site_na', 'unish'), $options + array('always-set' => NULL)); $this->drush('variable-get', array('site_na'), $options + array('exact' => NULL)); $var_export = $this->getOutput(); eval($var_export); $this->assertEquals('unish', $variables['site_na'], '--always-set option works as expected.'); $this->drush('variable-set', array('site_n', 'exactish'), $options + array('exact' => NULL)); $this->drush('variable-get', array('site_n'), $options + array('exact' => NULL)); $var_export = $this->getOutput(); eval($var_export); $this->assertEquals('exactish', $variables['site_n'], '--exact option works as expected.'); $this->drush('variable-delete', array('site_name'), $options); $output = $this->getOutput(); $this->assertEmpty($output, 'Variable was successfully deleted.'); } } drush-5.10.0/tests/watchdogTest.php000066400000000000000000000034401222105546100172340ustar00rootroot00000000000000setUpDrupal(1, TRUE); $options = array( 'yes' => NULL, 'root' => $this->webroot(), 'uri' => key($sites), ); // Enable dblog module and verify that the watchdog messages are listed $this->drush('pm-enable', array('dblog'), $options); $this->drush('watchdog-show', array(), $options); $output = $this->getOutput(); $this->assertContains('dblog module installed.', $output); $this->assertContains('dblog module enabled.', $output); // Add a new entry with a long message with the letter 'd' and verify that watchdog-show does // not print it completely in the listing unless --full is given. // As the output is formatted so lines may be splitted, assertContains does not work // in this scenario. Therefore, we will count the number of times a character is present. $message_chars = 300; $char = '*'; $message = str_repeat($char, $message_chars); $this->drush('php-eval', array("watchdog('drush', '" . $message . "')"), $options); $this->drush('watchdog-show', array(), $options); $output = $this->getOutput(); $this->assertGreaterThan(substr_count($output, $char), $message_chars); $this->drush('watchdog-show', array(), $options + array('full' => NULL)); $output = $this->getOutput(); $this->assertGreaterThanOrEqual($message_chars, substr_count($output, $char)); // Tests message deletion $this->drush('watchdog-delete', array('all'), $options); $output = $this->getOutput(); $this->drush('watchdog-show', array(), $options); $output = $this->getOutput(); $this->assertEmpty($output); } }