From bed0f3ea88a24874e677cf13af2c1131c4d7c136 Mon Sep 17 00:00:00 2001 From: Tim Steiner <tsteiner2@unl.edu> Date: Tue, 13 Jul 2010 19:05:27 +0000 Subject: [PATCH] Checking in the drush module for use in automated site creation. git-svn-id: file:///tmp/wdn_thm_drupal/branches/drupal-7.x@135 20a16fea-79d4-4915-8869-1ea9d5ebf173 --- sites/all/modules/drush/LICENSE.txt | 274 +++ sites/all/modules/drush/README.txt | 162 ++ .../drush/commands/core/clear.cache.inc | 60 + .../drush/commands/core/core.drush.inc | 847 ++++++++ .../drush/commands/core/drupal/batch_6.inc | 194 ++ .../drush/commands/core/drupal/batch_7.inc | 251 +++ .../commands/core/drupal/environment_5.inc | 214 ++ .../commands/core/drupal/environment_6.inc | 207 ++ .../commands/core/drupal/environment_7.inc | 175 ++ .../drush/commands/core/drupal/update_5.inc | 120 ++ .../drush/commands/core/drupal/update_6.inc | 480 +++++ .../drush/commands/core/drupal/update_7.inc | 322 +++ .../drush/commands/core/rsync.core.inc | 244 +++ .../modules/drush/commands/core/scratch.php | 20 + .../drush/commands/core/search.drush.inc | 89 + .../commands/core/site_install.drush.inc | 134 ++ .../drush/commands/core/sitealias.drush.inc | 262 +++ .../drush/commands/core/upgrade.drush.inc | 165 ++ .../drush/commands/core/variable.drush.inc | 205 ++ .../drush/commands/core/watchdog.drush.inc | 374 ++++ .../drush/commands/pm/package_handler/cvs.inc | 125 ++ .../commands/pm/package_handler/wget.inc | 103 + .../modules/drush/commands/pm/pm.drush.inc | 1475 ++++++++++++++ .../commands/pm/update_info/drupal_5.inc | 94 + .../commands/pm/update_info/drupal_6.inc | 86 + .../commands/pm/update_info/drupal_7.inc | 91 + .../drush/commands/pm/updatecode.pm.inc | 438 ++++ .../commands/pm/version_control/backup.inc | 60 + .../drush/commands/pm/version_control/bzr.inc | 126 ++ .../drush/commands/pm/version_control/svn.inc | 129 ++ .../modules/drush/commands/sql/sql.drush.inc | 559 +++++ .../modules/drush/commands/sql/sync.sql.inc | 344 ++++ sites/all/modules/drush/drush | 53 + sites/all/modules/drush/drush.api.php | 157 ++ sites/all/modules/drush/drush.bat | 3 + sites/all/modules/drush/drush.info | 7 + sites/all/modules/drush/drush.php | 218 ++ sites/all/modules/drush/drush_logo-black.png | Bin 0 -> 23280 bytes .../examples/example.aliases.drushrc.php | 152 ++ .../drush/examples/example.drushrc.php | 168 ++ .../modules/drush/examples/sandwich.drush.inc | 121 ++ sites/all/modules/drush/examples/sandwich.txt | 24 + sites/all/modules/drush/includes/backend.inc | 476 +++++ sites/all/modules/drush/includes/batch.inc | 69 + sites/all/modules/drush/includes/command.inc | 824 ++++++++ sites/all/modules/drush/includes/context.inc | 560 +++++ sites/all/modules/drush/includes/drush.inc | 1794 +++++++++++++++++ .../modules/drush/includes/environment.inc | 1099 ++++++++++ .../all/modules/drush/includes/sitealias.inc | 1506 ++++++++++++++ sites/all/modules/drush/includes/table.inc | 893 ++++++++ 50 files changed, 16553 insertions(+) create mode 100644 sites/all/modules/drush/LICENSE.txt create mode 100644 sites/all/modules/drush/README.txt create mode 100644 sites/all/modules/drush/commands/core/clear.cache.inc create mode 100644 sites/all/modules/drush/commands/core/core.drush.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/batch_6.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/batch_7.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/environment_5.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/environment_6.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/environment_7.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/update_5.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/update_6.inc create mode 100644 sites/all/modules/drush/commands/core/drupal/update_7.inc create mode 100644 sites/all/modules/drush/commands/core/rsync.core.inc create mode 100644 sites/all/modules/drush/commands/core/scratch.php create mode 100644 sites/all/modules/drush/commands/core/search.drush.inc create mode 100644 sites/all/modules/drush/commands/core/site_install.drush.inc create mode 100644 sites/all/modules/drush/commands/core/sitealias.drush.inc create mode 100644 sites/all/modules/drush/commands/core/upgrade.drush.inc create mode 100644 sites/all/modules/drush/commands/core/variable.drush.inc create mode 100644 sites/all/modules/drush/commands/core/watchdog.drush.inc create mode 100644 sites/all/modules/drush/commands/pm/package_handler/cvs.inc create mode 100644 sites/all/modules/drush/commands/pm/package_handler/wget.inc create mode 100644 sites/all/modules/drush/commands/pm/pm.drush.inc create mode 100644 sites/all/modules/drush/commands/pm/update_info/drupal_5.inc create mode 100644 sites/all/modules/drush/commands/pm/update_info/drupal_6.inc create mode 100644 sites/all/modules/drush/commands/pm/update_info/drupal_7.inc create mode 100644 sites/all/modules/drush/commands/pm/updatecode.pm.inc create mode 100644 sites/all/modules/drush/commands/pm/version_control/backup.inc create mode 100644 sites/all/modules/drush/commands/pm/version_control/bzr.inc create mode 100644 sites/all/modules/drush/commands/pm/version_control/svn.inc create mode 100644 sites/all/modules/drush/commands/sql/sql.drush.inc create mode 100644 sites/all/modules/drush/commands/sql/sync.sql.inc create mode 100755 sites/all/modules/drush/drush create mode 100644 sites/all/modules/drush/drush.api.php create mode 100644 sites/all/modules/drush/drush.bat create mode 100644 sites/all/modules/drush/drush.info create mode 100755 sites/all/modules/drush/drush.php create mode 100644 sites/all/modules/drush/drush_logo-black.png create mode 100644 sites/all/modules/drush/examples/example.aliases.drushrc.php create mode 100644 sites/all/modules/drush/examples/example.drushrc.php create mode 100644 sites/all/modules/drush/examples/sandwich.drush.inc create mode 100644 sites/all/modules/drush/examples/sandwich.txt create mode 100644 sites/all/modules/drush/includes/backend.inc create mode 100644 sites/all/modules/drush/includes/batch.inc create mode 100644 sites/all/modules/drush/includes/command.inc create mode 100644 sites/all/modules/drush/includes/context.inc create mode 100644 sites/all/modules/drush/includes/drush.inc create mode 100644 sites/all/modules/drush/includes/environment.inc create mode 100644 sites/all/modules/drush/includes/sitealias.inc create mode 100644 sites/all/modules/drush/includes/table.inc diff --git a/sites/all/modules/drush/LICENSE.txt b/sites/all/modules/drush/LICENSE.txt new file mode 100644 index 00000000..2c095c8d --- /dev/null +++ b/sites/all/modules/drush/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public License +applies to most of the Free Software Foundation's software and to any other +program whose authors commit to using it. (Some other Free Software +Foundation software is covered by the GNU Library General Public License +instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this service if +you wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems +introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND + MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such +program or work, and a "work based on the Program" means either the +Program or any derivative work under copyright law: that is to say, a work +containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, translation +is included without limitation in the term "modification".) Each licensee is +addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made +by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you +also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print such +an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you distribute +them as separate works. But when you distribute the same sections as part +of a whole which is a work based on the Program, the distribution of the +whole must be on the terms of this License, whose permissions for other +licensees extend to the entire whole, and thus to each and every part +regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to +control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the scope +of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above +on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for +noncommercial distribution and only if you received the program in object +code or executable form with such an offer, in accord with Subsection b +above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source code +means all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation and +installation of the executable. However, as a special exception, the source +code distributed need not include anything that is normally distributed (in +either source or binary form) with the major components (compiler, kernel, +and so on) of the operating system on which the executable runs, unless that +component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with the +object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will automatically +terminate your rights under this License. However, parties who have received +copies, or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the +Program (or any work based on the Program), you indicate your acceptance +of this License to do so, and all its terms and conditions for copying, +distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that +version or of any later version published by the Free Software Foundation. If +the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make +exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT +PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR +AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR +ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN +IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/sites/all/modules/drush/README.txt b/sites/all/modules/drush/README.txt new file mode 100644 index 00000000..da17c57a --- /dev/null +++ b/sites/all/modules/drush/README.txt @@ -0,0 +1,162 @@ +// $Id: README.txt,v 1.46 2010/06/16 15:08:02 weitzman Exp $ + +DESCRIPTION +----------- +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/translations. Similarly, it runs update.php, executes sql +queries and DB migrations, and misc utilities like run cron or clear cache. + +INSTALLATION +------------ +For Linux/Unix/Mac: + 1. Untar the tarball into a folder outside of your web site (/path/to/drush) + (e.g. if drush is in your home directory, ~/drush can be used for /path/to/drush) + 2. Make the 'drush' command executable: + $ chmod u+x /path/to/drush/drush + 3. (Optional, but recommended:) To ease the use of drush, + - create a link to drush in a directory that is in your $PATH, e.g.: + $ ln -s /path/to/drush/drush /usr/local/bin/drush + OR + - create an alias to drush: + $ alias drush='/path/to/drush/drush' + For example, if drush is in your home directory: + $ alias drush='~/drush/drush' + This goes into .profile, .bash_aliases or .bashrc in your home folder. + NOTE: You must log out and then log back in again or re-load your bash + configuration file to apply your changes to your current session: + $ source .bashrc + + NOTE FOR ADVANCED USERS + - If you want to run drush with a specific version of php, rather than the + one found by the drush command, you can instead create an alias that + executes the drush.php file directly: + $ alias drush='/path/to/php/php5 /path/to/drush/drush.php' + If you do this, to allow Drush to detect the number of available columns, + you need to add the line 'export COLUMNS' to the .profile file in your + home folder. + + 4. Start using drush by running "drush" from your Drupal root directory. + + (or, if you did not follow step 3, by running "/path/to/drush/drush" + or navigating to /path/to/drush and running "./drush" ) + + If you have troubles, try using the -l and -r options when invoking drush. See below. + +For Windows: + - Follow step 1. Use drush by navigating to /path/to/drush + and running 'drush.bat'. + - Whenever the documentation or the help text refers to + 'drush [option] <command>' or something similar, 'drush' has to be replaced + by 'drush.bat'. + - If drush.bat is not working for you, either add the directory in which your + php.exe resides to your PATH or edit drush.bat to point to your php.exe. + +USAGE +----- +Once installed and setup, you can use drush as follows while in +any Drupal directory: + + $ drush [options] <command> [argument1] [argument2] + +Use the 'help' command to get a list of available options and commands: + + $ drush help + +For multisite installations, you might need to use the -l or other command line +options just to get drush to work: + + $ drush -l http://example.com help + +Related Options: + -r <path>, --root=<path> Drupal root directory to use + (default: current directory or anywhere in a Drupal directory tree) + -l <uri> , --uri=<uri> URI of the drupal site to use + (only needed in multisite environments) + -v, --verbose Display verbose output. + --php The absolute path to your php binary. + +NOTE: 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. + +The drush cli command provide a customized bash shell with support for handy new +functions like cdd which whisks you to any directory in your drupal site. + +Many commands support a --pipe option which returns machine readable output. See +`drush pm-list --status=enabled --pipe` as an example + +EXAMPLES +-------- +Inside the "examples" folder 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 +(example.drush.inc). + +DRUSHRC.PHP +-------- +If you get tired of typing options all the time, you can add them to your drush.php alias or +create a drushrc.php file. These provide additional options for your drush call. They provide +great flexibility for a multi-site installation, for example. See example.drushrc.php. + +SITE ALIASES +-------- +Drush lets you run commands on a remote server, or even on a set of remote servers. +See http://drupal.org/node/670460 and example.aliases.drushrc.php for more information. + +COMMANDS +-------- +Drush ships with a number of commands, but you can easily write +your own. In fact, writing a drush command is no harder than writing simple +Drupal modules, since drush command files closely follow the structure of +ordinary Drupal modules. + +See example.drush.inc for light details on the internals of a drush command file. +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: + + - In a folder specified with the --include option (see above). + - Along with one of your existing modules. If your command is related to an + existing module, this is the preferred approach. + - In a .drush folder in your HOME folder. Note, that you have to create the + .drush folder yourself. + - In the system-wide drush commands folder, e.g. /usr/share/drush/commands + +In any case, it is important that you end the filename with ".drush.inc", so +that drush can find it. + +REQUIREMENTS +------------ +* To use drush from the command line, you'll need a CLI-mode capable PHP + binary. The minimum PHP version is 5.2. +* drush also runs on Windows; however, drush commands make use of + unix command line tools, so to use it effectively, you have to install + some of them, e.g. from GnuWin32 (http://gnuwin32.sourceforge.net/). More info + about Drush on Windows available at http://drupal.org/node/594744. +* Drush works with Drupal 5, Drupal 6 and Drupal 7. However, occasionally + recent changes to the most recent version of Drupal can introduce issues + with drush. On Drupal 5, drush requires update_status v5.x-2.5 or later + in order to use pm-updatecode. If you have an earlier version of update_status, + upgrade it via "drush dl update_status" before using pm-updatecode. + +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. + +CREDITS +------- +Originally developed by Arto Bendiken <http://bendiken.net/> for Drupal 4.7. +Redesigned by Franz Heinzmann (frando) <http://unbiskant.org/> in May 2007 for Drupal 5. +Maintained by Moshe Weitzman <http://drupal.org/moshe> with much help from +Owen Barton, Adrian Rossouw, greg.1.anderson. diff --git a/sites/all/modules/drush/commands/core/clear.cache.inc b/sites/all/modules/drush/commands/core/clear.cache.inc new file mode 100644 index 00000000..34699f12 --- /dev/null +++ b/sites/all/modules/drush/commands/core/clear.cache.inc @@ -0,0 +1,60 @@ +<?php + +/** + * Command callback for drush cache-clear. + */ + function drush_core_cache_clear($type = NULL) { + switch (drush_drupal_major_version()) { + case 5: + // clear preprocessor cache + drupal_clear_css_cache(); + + // clear core tables + $core = array('cache', 'cache_filter', 'cache_menu', 'cache_page'); + $alltables = array_merge($core, module_invoke_all('devel_caches')); + foreach ($alltables as $table) { + cache_clear_all('*', $table, TRUE); + } + drush_print(dt('Cache cleared.')); + break; + case 6: + case 7: + default: + $types = drush_cache_clear_types(); + if ($type) { + drush_op('call_user_func', $types[$type]); + drush_log(dt("'!name' cache was cleared", array('!name' => $type)), 'success'); + } + else { + $choice = drush_choice($types, 'Enter a number to choose which cache to clear.', '!key'); + if ($choice !== FALSE) { + call_user_func($types[$choice]); + drush_log(dt("'!name' cache was cleared", array('!name' => $choice)), 'success'); + } + } + break; + } +} + +function drush_cache_clear_types() { + $types = array( + 'all' => 'drupal_flush_all_caches', + 'theme' => 'drush_cache_clear_theme_registry', + 'menu' => 'menu_rebuild', + 'css+js' => 'drush_cache_clear_css_js', + ); + if (count(module_implements('node_grants'))) { + $types['nodeaccess'] = 'node_access_rebuild'; + } + return $types; +} + +function drush_cache_clear_theme_registry() { + drush_db_delete('cache', 'cid LIKE :theme_registry', array(':theme_registry' => 'theme_registry%')); +} + +function drush_cache_clear_css_js() { + _drupal_flush_css_js(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); +} diff --git a/sites/all/modules/drush/commands/core/core.drush.inc b/sites/all/modules/drush/commands/core/core.drush.inc new file mode 100644 index 00000000..906d12d9 --- /dev/null +++ b/sites/all/modules/drush/commands/core/core.drush.inc @@ -0,0 +1,847 @@ +<?php +// $Id: core.drush.inc,v 1.103.2.2 2010/07/01 15:25:06 weitzman Exp $ + +/** + * @file + * Core drush commands. + */ + +/** + * Implementation of hook_drush_command(). + * + * In this hook, you specify which commands your + * drush module makes available, what it does and + * description. + * + * Notice how this structure closely resembles how + * you define menu hooks. + * + * @return + * An associative array describing your command(s). + */ +function core_drush_command() { + $items = array(); + + $items['help'] = array( + 'description' => 'Print this help message. Use --filter to limit command list to one command file (e.g. --filter=pm)', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap. + 'options' => drush_get_option_help(), + '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.', + 'drush help --pipe' => 'A list of available commands, one per line.', + 'drush help --html' => 'Print help for all commands in HTML format.', + ), + ); + $items['core-cron'] = array( + 'description' => 'Run all cron hooks.', + 'aliases' => array('cron'), + ); + $items['updatedb'] = array( + 'description' => dt('Execute the update.php process from the command line'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + 'aliases' => array('updb'), + ); + $items['core-status'] = array( + 'description' => 'Provides a birds-eye view of the current Drupal installation, if any.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'aliases' => array('status', 'st'), + 'examples' => array( + 'drush status version' => 'Show all status lines that contain version information.', + 'drush status --pipe' => 'A list key=value items separated by line breaks.', + 'drush status drush-version --pipe' => 'Emit just the drush version with no label.', + ), + 'arguments' => array( + 'item' => 'Optional. The status item line(s) to display. Any matching line is shown; if only one line matches, then only the value is displayed. Otherwise, key=value is output.', + ), + 'options' => array( + 'show-passwords' => 'Show database password.', + ) + ); + $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.', + ), + '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' => "Additional paths to search for scripts. Use POSIX path separator (':') for multiple paths.", + ), + 'aliases' => array('scr'), + 'deprecated-aliases' => array('script'), + ); + $items['cache-clear'] = array( + '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.', + ), + 'aliases' => array('cc'), + ); + $items['search-status'] = array( + 'description' => '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.', + ), + ); + $items['core-rsync'] = array( + 'description' => 'Rsync the Drupal tree to/from another server using ssh. Relative paths start from the Drupal root folder if a site alias is used; otherwise they start from the current working directory.', + '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 -az.', + '--RSYNC-FLAG' => 'Most rsync flags passed to drush sync will be passed on to rsync. See rsync documentation.', + '--exclude-conf' => 'Excludes settings.php from being rsynced. Default.', + '--include-conf' => 'Allow settings.php to be rsynced', + '--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' => 'Coma-separated list of paths to exclude.', + '--include-paths' => 'Coma-separated list of paths to include.', + ), + 'examples' => array( + 'drush rsync @dev @stage' => 'Rsync Drupal root from dev to 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 stage.', + ), + 'aliases' => array('rsync'), + 'deprecated-aliases' => array('sync'), + ); + $items['php-eval'] = array( + 'description' => 'Evaluate arbitrary php code after bootstrapping Drupal.', + 'examples' => array( + 'drush php-eval "variable_set(\'hello\', \'world\');"' => 'Sets the hello variable using Drupal API.', + ), + 'arguments' => array( + 'code' => 'PHP code', + ), + 'deprecated-aliases' => array('eval'), + ); + $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\'', + ), + 'options' => array( + 'db-url' => 'A Drupal 5/6 style database URL. Only required for initial install - not re-install.', + 'db-prefix' => 'An optional table prefix to use for initial install.', + 'account-name' => 'uid1 name. defaults to admin', + 'account-pass' => 'uid1 pass. defaults to admin', + 'account-mail' => 'uid1 email. defaults to admin@example.com', + 'locale' => 'A short language code. Sets the default site language. Language files must already be present. You may use download command to get them.', + 'clean-url'=> 'Defaults to 1', + 'site-name' => 'Defaults to Site-Install', + 'site-mail' => 'From: for system mailings. Defaults to admin@example.com', + 'sites-subdir' => "Name of directory under 'sites' which should be created if needed. Defaults to 'default'", + ), + '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 --account-user=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.', + ), + 'core' => array(7), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT, + 'aliases' => array('si'), + 'deprecated-aliases' => array('installsite', 'is'), + ); + + $items['drupal-directory'] = array( + 'description' => dt('Return path to a given module/theme directory. See --help for more details.'), + '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.', + ), + '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['core-cli'] = array( + 'description' => dt('Enter a new shell optimized for drush use.'), + 'examples' => array( + 'help' => 'Print available drush commands', + 'cdd' => 'Navigate to the root of your Drupal site', + 'cdd files' => 'Navigate to the files directory.', + 'lsd files' => 'List all files in the Drupal files directory.', + 'on @alias status' => 'Run the command "status" on the site indicated by @alias', + ), + 'aliases' => array('cli'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + ); + + $items['batch-process'] = array( + 'description' => dt('Process operations in the specified batch set'), + 'hidden' => TRUE, + 'arguments' => array( + 'batch-id' => 'The batch id that will be processed', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + ); + + $items['updatedb-batch-process'] = array( + 'description' => dt('Perform update functions'), + 'hidden' => TRUE, + 'arguments' => array( + 'batch-id' => 'The batch id that will be processed', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE, + ); + + return $items; +} + +function core_drush_engine_drupal() { + $engines = array(); + $engines['batch'] = array(); + $engines['update'] = array(); + $engines['environment'] = array(); + return $engines; +} + +/** + * Command handler. Execute update.php code from drush. + */ +function drush_core_updatedb() { + if (drush_get_context('DRUSH_SIMULATE')) { + return drush_set_error(dt('updatedb command does not support --simulate option.')); + } + + drush_include_engine('drupal', 'update', drush_drupal_major_version()); + update_main(); + + // Clear all caches. We just performed major surgery. + drush_drupal_cache_clear_all(); + + drush_log(dt('Finished performing updates.'), 'ok'); +} + +/** + * This is called if no command or an unknown command is entered. + */ +function drush_core_help() { + $commands = func_get_args(); + + if (drush_get_option('html')) { + return drush_print(drush_help_html()); + } + elseif (empty($commands)) { + drush_show_help(array('help')); + $phases = _drush_bootstrap_phases(); + // For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_SITE+1. + $phases = array_slice($phases, 0, DRUSH_BOOTSTRAP_DRUPAL_SITE+1); + drush_print(dt('Commands: ')); + + $printed_rows = array(); + $phase_index = DRUSH_BOOTSTRAP_DRUSH; + + foreach ($phases as $phase_index) { + if (drush_bootstrap_validate($phase_index)) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { + drush_bootstrap($phase_index); + } + + $commands = drush_get_commands(); + // Filter by command file if specified. + if ($commandfile = drush_get_option('filter')) { + foreach ($commands as $key => $candidate) { + if ($candidate['commandfile'] != $commandfile) { + unset($commands[$key]); + } + } + } + + $rows = array(); + ksort($commands); + foreach($commands as $key => $command) { + if (!$command['hidden']) { + if (!array_key_exists('is_alias', $command) || !$command['is_alias']) { + if (!array_key_exists($key, $printed_rows)) { + $name = $command['aliases'] ? $key . ' (' . implode(', ', $command['aliases']) . ')': $key; + $rows[$key] = array($name, $command['description']); + $pipe[] = "\"$key\""; + } + } + } + } + drush_print_table($rows, FALSE, array(0 => 20)); + $printed_rows = array_merge($printed_rows, $rows); + } + else { + break; + } + } + + // Newline-delimited list for use by other scripts. Set the --pipe option. + drush_print_pipe($pipe); + return; + } + else { + return drush_show_help($commands); + } + + drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => implode(" ", $commands)))); +} + + +/** + * Return an HTML page documenting all available commands and global options. + */ +function drush_help_html() { + foreach (drush_get_commands() as $key => $command) { + // Get rid of aliases in command list. + if (empty($command['is_alias']) && !$command['hidden']) { + $commands[$key] = $command; + } + } + unset($commands['help']); + + $output = "<html><head><title>drush help</title><style>dt {font-size: 110%; font-weight: bold}</style></head><body>\n"; + + // Command table + $output .= '<h3>Command list</h3><table>'; + foreach ($commands as $key => $command) { + $output .= "<tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n"; + } + $output .= "</table>\n"; + + // Global options + $options = drush_get_option_help(); + $output .= '<h3>Global Options</h3><table>'; + foreach ($options as $key => $value) { + $output .= "<tr><td>$key</td><td>" . $value . "</td></tr>\n"; + } + $output .= "</table>\n"; + + // Command details + $output .= '<h3>Command detail</h3><dl>'; + foreach ($commands as $key => $command) { + $output .= "\n<a name=\"$key\"></a><dt>$key</dt><dd><pre>\n"; + ob_start(); + drush_show_help(array($key)); + $output .= ob_get_clean(); + $output .= "</pre></dd>\n"; + } + + $output .= "</body></html>\n"; + + return $output; +} + +/** + * Implementation of hook_drush_help(). + * + * This function is called whenever a drush user calls + * 'drush help <name-of-your-command>' + * + * @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 'drush:help': + return dt('Execute a drush command. Run `drush help [command]` to view command-specific help.'); + case 'drush:cron': + return dt("Runs all cron hooks in all active modules for specified site."); + case 'drush:status': + return dt("View the Drupal version and DB credentials for the current site."); + 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 thats more self-documenting."); + case 'drush:cache-clear': + return dt("Delete a specific drupal cache, or all caches."); + case 'drush:search-status': + return dt("Show how many items remain to be indexed for search, and the total number of items."); + case 'drush:search-index': + return dt("Index the remaining search items."); + case 'drush:search-reindex': + return dt("Force the search index to be rebuilt."); + case 'drush:updatedb': + return dt("Run update.php just as a web browser would."); + case 'drush:rsync': + return dt("Sync the entire drupal directory or a subdirectory to a <destination> using ssh. Excludes .svn directories. Useful for pushing copies of your tree to a staging server, or retrieving a files directory from a remote site. Local paths should be specified relative to Drupal root."); + case 'drush:php-eval': + return dt("Run arbitrary PHP code in the context of Drupal"); + case 'drush:site-install': + return dt("Install Drupal using specified install profile."); + case 'drush:drupal-directory': + return dt("Return the filesystem path for projects/themes and other key folders. Used by `cli` command for handy commands `cdd` and `lsd`. If you want to use this command directly, you usually want to prefix the command with cd and enclose the command invocation in backticks. See Examples below."); + case 'drush:core-cli': + return dt("Enter a new shell optimized for drush use. See help for more details."); + case 'error:DRUSH_DRUPAL_DB_ERROR' : + $message = dt("Drush was not able to start (bootstrap) the Drupal database.\n"); + $message .= dt("Hint: This error often occurs when Drush is trying to bootstrap a site that has not been installed or does not have a configured database.\n"); + $message .= dt("\nDrush was attempting to connect to : \n!credentials\n", array('!credentials' => _core_site_credentials())); + $message .= dt("You can select another site with a working database setup by specifying the URI to use with the --uri parameter on the command line or \$options['uri'] in your drushrc.php file.\n"); + 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())); + $message .= dt("You can select another site with a working database setup by specifying the URI to use with the --uri parameter on the command line or \$options['uri'] in your drushrc.php file.\n"); + 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(); + $phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE'); + 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; + $paths['%modules'] = 'sites'.DIRECTORY_SEPARATOR.'all'.DIRECTORY_SEPARATOR.'modules'; + $paths['%themes'] = 'sites'.DIRECTORY_SEPARATOR.'all'.DIRECTORY_SEPARATOR.'themes'; + if (drush_drupal_major_version() >= 7) { + $paths['%files'] = DrupalPublicStreamWrapper::getDirectoryPath(); + $paths['%private'] = DrupalPrivateStreamWrapper::getDirectoryPath(); + } + elseif (function_exists('file_directory_path')) { + $paths['%files'] = file_directory_path(); + } + // 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 = '') { + $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']; + $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 (function_exists('php_ini_loaded_file')) { + // Function available on PHP >= 5.2.4, but we use it if available to help + // users figure out their php.ini issues. + $status_table['PHP configuration'] = php_ini_loaded_file(); + } + $status_table['Drush version'] = DRUSH_VERSION; + $status_table['Drush configuration'] = implode(' ', drush_get_context_options('context-path', TRUE)); + + // 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'; + + $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 cron hooks. + * + * This is where the action takes place. + * + * In this function, all of Drupals API is (usually) available, including + * any functions you have added in your own modules/themes. + * + * To print something to the terminal window, use drush_print(). + * + */ +function drush_core_cron() { + if (drupal_cron_run()) { + drush_log(dt('Cron run successfully.'), 'success'); + } + else { + drush_set_error('DRUSH_CRON_FAILED', dt('Cron run failed.')); + } +} + +/** + * Command callback. Provides a birds-eye view of the current Drupal + * installation. + */ +function drush_core_status() { + drush_bootstrap_max(); + $status_table = _core_site_status_table(drush_get_option('project','')); + // 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]); + } + } + } + drush_backend_set_result($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; +} + +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; +} + +/** + * Command callback. Runs "naked" php scripts. + */ +function drush_core_php_script() { + $args = func_get_args(); + + // 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(":", $script_path) as $path) { + $searchpath[] = $path; + } + } + + if (empty($args)) { + // List all available scripts. + $all = array(); + foreach($searchpath as $key => $path) { + $recurse = !$key == 'cwd'; + $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. + $script = $args[0]; + if (!preg_match('/\.php$/i', $script)) { + $script .= '.php'; + } + $found = FALSE; + foreach($searchpath as $path) { + $script_filename = $path . '/' . $script; + if (file_exists($script_filename)) { + include($script_filename); + $found = TRUE; + break; + } + $all[] = $script_filename; + } + + if (!$found) { + drush_set_error('Script not found.', dt('Unable to find any of the following: @files', array('@files' => implode(', ', $all)))); + } + } +} + +function drush_core_php_eval($command) { + eval($command . ';'); +} + +/** + * Process sets from the specified batch. + * + * This is the default batch processor that will be used if the $command parameter + * to drush_backend_batch_process() has not been specified. + */ +function drush_core_batch_process($id) { + drush_batch_command($id); +} + +/** + * Process outstanding updates during updatedb. + * + * This is a batch processing command that makes use of the drush_backend_invoke + * api. + * + * This command includes the version specific update engine, which correctly + * initialises the environment to be able to successfully handle minor and major + * upgrades. + */ +function drush_core_updatedb_batch_process($id) { + drush_include_engine('drupal', 'update', drush_drupal_major_version()); + _update_batch_command($id); +} + +function _drush_core_directory($target = 'root') { + // Normalize to a sitealias in the target. + $normalized_target = $target; + if (strpos($target, ':') === FALSE) { + if (substr($target,0,1) == '@') { + $normalized_target = $target; // . ':%site'; + } + else { + // @self makes no sense before 'site' level. + if(!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) { + return FALSE; + } + $normalized_target = '@self:'; + if (substr($target,0,1) != '%') { + $normalized_target .= '%'; + } + $normalized_target .= $target; + } + } + + $additional_options = array(); + $values = drush_sitealias_evaluate_path($normalized_target, $additional_options); + + if (isset($values['path'])) { + // Hurray, we found the destination + return $values['path']; + } + return NULL; +} + +function drush_core_drupal_directory($target = 'root') { + $path = _drush_core_directory($target); + + if (isset($path)) { + drush_print($path); + } + else { + return drush_set_error(dt('Core directory path !target not found.', array('!target' => $target))); + } + + return TRUE; +} + +function drush_core_find_project_path($target) { + $theme_suffix = drush_drupal_major_version() >= 6 ? '.info' : '/style.css'; + $masks = array( + conf_path() . '/modules' => "/^$target.module/", + 'profiles/default/modules' => "/^$target.module/", // Too early for variable_get('install_profile', 'default'); Just use default. + 'sites/all/modules' => "/^$target.module/", // Add all module paths, even disabled modules. + conf_path() . '/themes' => "/^$target" . "$theme_suffix/", + 'sites/all/themes' => "/^$target" . "$theme_suffix/", + ); + + $files = array(); + foreach ($masks as $key => $mask) { + if ($files = drush_scan_directory("$key", $mask, array('..', '.', 'CVS', '.svn', '.bzr'), 0, TRUE, 'name')) { + // Just use the first match. + $file = reset($files); + return drush_get_context('DRUSH_DRUPAL_ROOT') . '/' . dirname($file->filename); + } + } + + return NULL; +} + +function drush_core_cli() { + drush_bootstrap_max(); + + // Do not allow cli to start recursively, or from backend invoke. + if (drush_get_option('in-cli',FALSE)) { + return drush_set_error(dt('Already in drush core-cli; press control-d to exit.')); + } + if (drush_get_context('DRUSH_BACKEND')) { + return drush_set_error(dt('Cannot run drush core-cli from non-interactive mode; aborting.')); + } + + // Make sure that we call drush the same way that we were called. + $drush_command = DRUSH_COMMAND.' --in-cli'; + + $bashrc_data = implode("/n/n", drush_command_invoke_all('cli_bashrc', $drush_command)); + + // Before entering the new bash script, cd to the current site root, + // if any. This will make our default site for drush match the + // currently bootstrapped site (at least until the user cd's away). + if ($site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { + chdir($site_root); + } + + // Print our entire bashrc file in verbose mode + if (drush_get_context('DRUSH_VERBOSE')) { + drush_print($bashrc_data); + } + + drush_print("Entering the drush cli. Use CONTROL-D to exit."); + drush_print("Type 'help' for help."); + + // Save out bashrc in a temporary file and launch bash. + // control-d to exit. The temp file will be deleted after + // we exit. + $bashrc = drush_save_data_to_temp_file($bashrc_data); + drush_op('system', 'bash --rcfile ' . $bashrc . ' > `tty`'); +} + +// Implement our own hook_cli_bashrc() +function core_cli_bashrc($drush_command) { + // Set up our default bashrc file for the drush cli + $bashrc_data = <<<EOD + +PS1="drush> " + +alias on="$drush_command" +alias drush="$drush_command" +DRUSH_CLI=true + + +function cdd() { + TARGET= + PARAM=\$1 + if [ \$# -gt 1 ] ; then + TARGET=\$1 + PARAM=\$2 + fi + DEST=`drush \$TARGET drupal-directory \$PARAM` + if [ \$? != 0 ] || [ -z "\$DEST" ] + then + echo "Target \$1 was not found." + else + echo "cd \$DEST" + cd \$DEST; + fi +} + +function lsd() { + TARGET= + PARAM=\$1 + if [ \$# -gt 1 ] ; then + TARGET=\$1 + PARAM=\$2 + fi + DEST=`drush \$TARGET drupal-directory \$PARAM` + if [ \$? != 0 ] || [ -z "\$DEST" ] + then + echo "Target \$1 was not found." + else + echo "ls \$DEST" + ls \$DEST; + fi +} + +EOD; + + // Add aliases for all of our commands + $commands = drush_get_commands(); + foreach ($commands as $key => $command) { + // Filter out old commands that still have spaces + if (strpos($key, ' ') === FALSE) { + $bashrc_data .= "function $key() {\n $drush_command $key \"\$@\"\n}\n"; + } + } + + return $bashrc_data; +} diff --git a/sites/all/modules/drush/commands/core/drupal/batch_6.inc b/sites/all/modules/drush/commands/core/drupal/batch_6.inc new file mode 100644 index 00000000..df64a725 --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/batch_6.inc @@ -0,0 +1,194 @@ +<?php +/** + * @file + * Drupal 6 engine for the Batch API + */ + +/** + * Main loop for the Drush batch API. + * + * Saves a record of the batch into the database, and progressively call $command to + * process the operations. + * + * @param command + * The command to call to process the batch. + * + */ +function _drush_backend_batch_process($command = 'batch-process') { + $batch =& batch_get(); + + if (isset($batch)) { + $process_info = array( + 'current_set' => 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'); + + // 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_backend_invoke($command, array($batch['id'])); + + $finished = drush_get_error() || !$data || ($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); + // Process the current operation. + call_user_func_array($function, array_merge($args, array(&$batch_context))); + } + + 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']); + } +} diff --git a/sites/all/modules/drush/commands/core/drupal/batch_7.inc b/sites/all/modules/drush/commands/core/drupal/batch_7.inc new file mode 100644 index 00000000..081bc3c3 --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/batch_7.inc @@ -0,0 +1,251 @@ +<?php +/** + * @file + * Drupal 7 engine for the Batch API + */ + +/** + * Main loop for the Drush batch API. + * + * Saves a record of the batch into the database, and progressively call $command to + * process the operations. + * + * @param command + * The command to call to process the batch. + * + */ +function _drush_backend_batch_process($command = 'batch-process') { + $batch =& batch_get(); + + if (isset($batch)) { + $process_info = array( + 'current_set' => 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(); + + $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; + + while (!$finished) { + $data = drush_backend_invoke($command, array($batch['id'])); + $finished = drush_get_error() || !$data || ($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, + ); + call_user_func_array($function, array_merge($args, array(&$batch_context))); + + 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(); + } +} + + diff --git a/sites/all/modules/drush/commands/core/drupal/environment_5.inc b/sites/all/modules/drush/commands/core/drupal/environment_5.inc new file mode 100644 index 00000000..145dcd5f --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/environment_5.inc @@ -0,0 +1,214 @@ +<?php +// $Id: environment_5.inc,v 1.10 2010/04/06 13:07:19 weitzman Exp $ +/** + * @file + * Specific functions for a drupal 5 environment. + * drush_include_engine() magically includes either this file + * or environment_X.inc depending on which version of drupal drush + * is called from. + */ + +/** + * Get complete information for all available modules. + * + * We need to set the type for those modules that are not already in the system table. + * Also In Drupal 5, system_modules() returns NULL for the dependency list of the module if there are no dependencies. + * We will override this to be an empty array instead to be compatible to Drupal 6 and 7. + * + * @return + * An array containing module info for all available modules. + */ +function drush_get_modules() { + $modules = module_rebuild_cache(); + foreach ($modules as $module) { + if (!isset($module->type)) { + $module->type = 'module'; + } + if (empty($module->info['dependencies'])) { + $module->info['dependencies'] = array(); + } + if (empty($module->info['dependents'])) { + $module->info['dependents'] = array(); + } + } + + return $modules; +} + +/** + * 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 = $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))) + ); + } + else { + $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) { + // In Drupal 5, drupal_install_modules() only installs new modules, + // and does not enable previously installed and disabled modules. + $install_modules = array(); + $enable_modules = array(); + + foreach ($modules as $module) { + if (drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { + $install_modules[] = $module; + } + else { + $enable_modules[] = $module; + } + } + drupal_install_modules($install_modules); + module_enable($enable_modules); +} + +/** + * 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); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + */ +function drush_module_uninstall($modules) { + require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/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) { + require_once('./'. drupal_get_path('module', 'system') .'/system.module'); + $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. + * + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes() { + $themes = system_theme_data(); + foreach ($themes as $theme) { + if (!isset($theme->type)) { + $theme->type = 'theme'; + } + } + + return $themes; +} + +/** + * Enable a list of themes. + * + * This function is based on system_themes_submit(). + * + * @see system_themes_submit() + * @param $themes + * Array of theme names. + */ +function drush_theme_enable($themes) { + foreach ($themes as $theme) { + system_initialize_theme_blocks($theme); + } + $placeholder = implode(',', array_fill(0, count($themes), "'%s'")); + db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".$placeholder.")", $themes); + menu_rebuild(); +} + +/** + * Disable a list of themes. + * + * This function is based on system_themes_submit(). + * + * @see system_themes_submit() + * @param $themes + * Array of theme names. + */ +function drush_theme_disable($themes) { + $placeholder = implode(',', array_fill(0, count($themes), "'%s'")); + db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".$placeholder.")", $themes); + menu_rebuild(); +} + +/** + * 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_NOTICE => 'notice', + WATCHDOG_WARNING => 'warning', + WATCHDOG_ERROR => 'error' + ); +} diff --git a/sites/all/modules/drush/commands/core/drupal/environment_6.inc b/sites/all/modules/drush/commands/core/drupal/environment_6.inc new file mode 100644 index 00000000..50495886 --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/environment_6.inc @@ -0,0 +1,207 @@ +<?php +// $Id: environment_6.inc,v 1.9 2010/04/06 13:07:19 weitzman Exp $ +/** + * @file + * Specific functions for a drupal 5 environment. + * drush_include_engine() magically includes either this file + * or environment_X.inc depending on which version of drupal drush + * is called from. + */ + +/** + * Get complete information for all available modules. + * + * We need to set the type for those modules that are not already in the system table. + * + * @return + * An array containing module info for all available modules. + */ +function drush_get_modules() { + $modules = module_rebuild_cache(); + foreach ($modules as $module) { + if (!isset($module->type)) { + $module->type = 'module'; + } + } + + return $modules; +} + + +/** + * 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 = $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))) + ); + } + else { + $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); +} + +/** + * 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); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + */ +function drush_module_uninstall($modules) { + require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/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) { + 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. + * + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes() { + $themes = system_theme_data(); + foreach ($themes as $theme) { + if (!isset($theme->type)) { + $theme->type = 'theme'; + } + } + + 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', + ); +} diff --git a/sites/all/modules/drush/commands/core/drupal/environment_7.inc b/sites/all/modules/drush/commands/core/drupal/environment_7.inc new file mode 100644 index 00000000..8f16f95f --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/environment_7.inc @@ -0,0 +1,175 @@ +<?php +// $Id: environment_7.inc,v 1.12 2010/03/17 20:33:25 weitzman Exp $ +/** + * @file + * Specific functions for a drupal 5 environment. + * drush_include_engine() magically includes either this file + * or environment_X.inc depending on which version of drupal drush + * is called from. + */ + +/** + * Get complete information for all available modules. + * + * @return + * An array containing module info for all installed modules. + */ +function drush_get_modules() { + return system_rebuild_module_data(); +} + +/** + * 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 = $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']; + $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]['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, 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) { + module_enable($modules, FALSE); +} + +/** + * 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); +} + +/** + * Uninstall a list of modules. + * + * @param $modules + * Array of module names + */ +function drush_module_uninstall($modules) { + require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc'; + drupal_uninstall_modules($modules); +} + +/** + * 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) { + module_load_include('inc', 'system', 'system.admin'); + $form_state = array('values' => array('status' => $active_modules)); + drupal_form_submit('system_modules', $form_state); +} + +/** + * Get complete information for all available themes. + * + * @return + * An array containing theme info for all available themes. + */ +function drush_get_themes() { + return system_rebuild_theme_data(); +} + +/** + * 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', + ); +} diff --git a/sites/all/modules/drush/commands/core/drupal/update_5.inc b/sites/all/modules/drush/commands/core/drupal/update_5.inc new file mode 100644 index 00000000..e925044a --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/update_5.inc @@ -0,0 +1,120 @@ +<?php +// $Id: update_5.inc,v 1.8 2009/12/06 12:53:38 weitzman Exp $ + +/** + * @file + * Update.php for provisioned sites. + * This file is a derivative of the standard drupal update.php, + * which has been modified to allow being run from the command + * line. + */ + + +ob_start(); +include_once("update.php"); +ob_end_clean(); + +function update_main() { + // 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); + } + + include_once './includes/install.inc'; + drupal_load_updates(); + + update_fix_schema_version(); + update_fix_watchdog_115(); + update_fix_watchdog(); + update_fix_sessions(); + $has_updates = FALSE; + + $start = array(); + foreach (module_list() as $module) { + + $updates = drupal_get_schema_versions($module); + if ($updates !== FALSE) { + + $pending[$module] = array(); + $updates = drupal_map_assoc($updates); + + $schema_version = drupal_get_installed_schema_version($module); + $default = $schema_version; + + foreach (array_keys($updates) as $update) { + if ($update > $default) { + $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?'))) { + drush_die('Aborting.'); + } + + $update_results = 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) { + $finished = FALSE; + if ($update >= $version) { + while (!$finished) { + // do update + $ret = module_invoke($module, 'update_' . $update); + _drush_log_update_sql($ret); + $finished = 1; + if (isset($ret['#finished'])) { + $finished = $ret['#finished']; + unset($ret['#finished']); + } + } + drupal_set_installed_schema_version($module, $update); + } + } + } + else { + drush_log(dt('No database updates for @module module', array('@module' => $module)), 'success'); + } + } + } + else { + drush_log(dt("No database updates required"), 'success'); + } +} + + diff --git a/sites/all/modules/drush/commands/core/drupal/update_6.inc b/sites/all/modules/drush/commands/core/drupal/update_6.inc new file mode 100644 index 00000000..21278b3d --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/update_6.inc @@ -0,0 +1,480 @@ +<?php +// $Id: update_6.inc,v 1.17 2010/04/12 14:37:10 weitzman Exp $ +/** + * @file + * Update.php for provisioned sites. + * This file is a derivative of the standard drupal update.php, + * which has been modified to allow being run from the command + * line. + */ +define('MAINTENANCE_MODE', 'update'); + +/** + * Add a column to a database using syntax appropriate for PostgreSQL. + * Save result of SQL commands in $ret array. + * + * Note: when you add a column with NOT NULL and you are not sure if there are + * already rows in the table, you MUST also add DEFAULT. Otherwise PostgreSQL + * won't work when the table is not empty, and db_add_column() will fail. + * To have an empty string as the default, you must use: 'default' => "''" + * 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']); + } + + 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. + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION); + 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); + 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_projects(); + + 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 <a href="http://drupal.org/upgrade">need to upgrade</a> 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?'))) { + drush_die('Aborting.'); + } + // 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); +} + diff --git a/sites/all/modules/drush/commands/core/drupal/update_7.inc b/sites/all/modules/drush/commands/core/drupal/update_7.inc new file mode 100644 index 00000000..e7fb9052 --- /dev/null +++ b/sites/all/modules/drush/commands/core/drupal/update_7.inc @@ -0,0 +1,322 @@ +<?php +// $Id: update_7.inc,v 1.26 2010/06/21 19:18:01 weitzman Exp $ +/** + * @file + * Update.php for provisioned sites. + * This file is a derivative of the standard drupal update.php, + * which has been modified to allow being run from the command + * line. + */ + +/** + * Global flag to identify update.php run, and so avoid various unwanted + * operations, such as hook_init() and hook_exit() invokes, css/js preprocessing + * and translation, and solve some theming issues. This flag is checked on several + * places in Drupal code (not just update.php). + */ +define('MAINTENANCE_MODE', 'update'); + +/** + * Returns (and optionally stores) extra requirements that only apply during + * particular parts of the update.php process. + */ +function update_extra_requirements($requirements = NULL) { + static $extra_requirements = array(); + if (isset($requirements)) { + $extra_requirements += $requirements; + } + return $extra_requirements; +} + +/** + * Perform one update and store the results which will later be displayed on + * the finished page. + * + * An update function can force the current and all later updates for this + * module to abort by returning a $ret array with an element like: + * $ret['#abort'] = array('success' => 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'] = 'Updating ' . check_plain($module) . ' module'; +} + + + +/** + * 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. + require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; + require_once DRUPAL_ROOT . '/includes/update.inc'; + require_once DRUPAL_ROOT . '/includes/common.inc'; + require_once DRUPAL_ROOT . '/includes/file.inc'; + require_once DRUPAL_ROOT . '/includes/entity.inc'; + include_once DRUPAL_ROOT . '/includes/unicode.inc'; + + update_prepare_d7_bootstrap(); + drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION); + + require_once DRUPAL_ROOT . '/includes/install.inc'; + require_once DRUPAL_ROOT . '/modules/system/system.install'; + + // Load module basics. + include_once DRUPAL_ROOT . '/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(); + + // Rescan and repopulate the system table to ensure we have a full picture + // of the platform. + drush_get_projects(); + + 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?'))) { + drush_die('Aborting.'); + } + + 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) { + // Clear the caches in case the data has been updated. + drupal_flush_all_caches(); +} + diff --git a/sites/all/modules/drush/commands/core/rsync.core.inc b/sites/all/modules/drush/commands/core/rsync.core.inc new file mode 100644 index 00000000..6f078873 --- /dev/null +++ b/sites/all/modules/drush/commands/core/rsync.core.inc @@ -0,0 +1,244 @@ +<?php +// $Id: rsync.core.inc,v 1.2 2010/06/11 19:17:42 greg1anderson Exp $ + +/** + * Entrypoint for drush rsync. + * + * @param source + * A site alias ("@dev") or site specification ("/path/to/drupal#mysite.com") + * followed by an optional path (":path/to/sync"), or any path + * that could be passed to rsync ("user@server.com:/path/to/dir/"). + * @param destination + * Same format as source. + * @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). + */ +function drush_core_rsync($source, $destination, $additional_options = array()) { + // Preflight destination in case it defines aliases used by the source + _drush_sitealias_preflight_path($destination); + // After preflight, evaluate file paths + $source_settings = drush_sitealias_evaluate_path($source, $additional_options); + $destination_settings = drush_sitealias_evaluate_path($destination, $additional_options); + $source_path = $source_settings['evaluated-path']; + $destination_path = $destination_settings['evaluated-path']; + + if (!isset($source_settings)) { + return drush_set_error('DRUSH_BAD_PATH', dt('Could not evaluate source path !path.', array('!path' => $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 destinaiton, 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.'); + drush_die('Aborting.'); + } + } + + // 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); + + // Go ahead and call rsync with the paths we determined + drush_core_call_rsync($source_path, $destination_path, $additional_options, $include_settings_is_default); + } +} + +/** + * 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 = ' --exclude="*.svn*"'; + $mode = '-az'; + // Process --include-path and --exclude-path options the same way + foreach (array('include', 'exclude') as $include_exclude) { + // Get the option --include-path or --exclude path 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(',', drush_get_option($include_exclude . '-path', '')); + 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 . '"'; + } + } + } + // 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"'; + } + if (_drush_rsync_option_exists('exclude-sites', $additional_options)) { + $options .= ' --include="sites/all" --exclude="sites/*"'; + } + if (_drush_rsync_option_exists('mode', $additional_options)) { + $mode = "-" . drush_get_option_override($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'; + } + $rsync_available_options = array( + // unary options + 'archive', // -a + 'recursive', // -r + 'relative', // -R + 'backup', // -b + 'update', // -u + 'checksum', // -c + 'dirs', // -d + 'links', // -l + 'copy-links', // -L + 'copy-dirlinks', // -k + 'keep-dirlinks', // -K + 'hard-links', // -H + 'perms', // -p + 'executability', // -E + 'acls', // -A + 'xattrs', // -X + 'owner', // -o + 'group', // -g + 'times', // -t + 'omit-dir-times', // -O + 'sparse', // -S + 'dry-run', // -n + 'whole-file', // -W + 'one-file-system', // -x + 'prune-empty-dirs', // -m + 'ignore-times', // -I + 'fuzzy', // -y + 'cvs-exclude', // -C + 'compress', // -Z + 'protect-args', // -s + '8-bit-output', // -8 + 'human-readable', // -h + 'itemize-changes', // -i + 'copy-unsafe-links', + 'safe-links', + 'no-implied-dirs', + 'inplace', + 'append', + 'append-verify', + 'existing', + 'remove-source-files', + 'delete', + 'delete-before', + 'delete-during', + 'delete-delay', + 'delete-after', + 'delete-excluded', + 'ignore-errors', + 'force', + 'ignore-existing', + 'partial', + 'delay-updates', + 'numeric-ids', + 'size-only', + 'blocking-io', + 'stats', + 'progress', + 'list-only', + // options with values + 'block-size', + 'backup-dir', + 'suffix', + 'chmod', + 'rsync-path', + 'modify-window', + 'compare-dest', + 'copy-dest', + 'link-dest', + 'skip-compress', + 'filter', + 'exclude', + 'include', + 'address', + 'port', + 'sockopts', + 'out-format', + 'bwlimit', + 'iconv', + 'checksum-seed', + 'max-delete', + 'max-size', + 'min-size', + 'partial-dir', + 'timeout', + 'temp-dir', + 'compress-level', + 'out-format', + 'protocol', + ); + foreach ($rsync_available_options as $test_option) { + $value = drush_get_option_override($additional_options, $test_option); + if (isset($value)) { + if ($value === TRUE) { + $options .= " --$test_option"; + } + else { + $options .= " --$test_option=" . escapeshellarg($value); + } + } + } + + $ssh_options = drush_get_option_override($additional_options, 'ssh-options', ''); + $exec = "rsync -e 'ssh $ssh_options' $mode$options $source $destination"; + + if ($live_output) { + $exec_result = drush_op('system', $exec) !== FALSE; + } + else { + $exec_result = drush_shell_exec($exec); + } + + return $exec_result; +} + +function _drush_rsync_option_exists($option, $additional_options) { + if (array_key_exists($option, $additional_options)) { + return TRUE; + } + else { + return drush_get_option($option, FALSE); + } +} diff --git a/sites/all/modules/drush/commands/core/scratch.php b/sites/all/modules/drush/commands/core/scratch.php new file mode 100644 index 00000000..986f29cc --- /dev/null +++ b/sites/all/modules/drush/commands/core/scratch.php @@ -0,0 +1,20 @@ +<?php + +/* + * @file + * Use this file as a php scratchpad for your Drupal site. You might want to + * load a node, change it, and call node_save($node), for example. If you have + * used the Execute PHP feature of devel.module, this is the drush equivalent. + * + * You may edit this file with whatever php you choose. Then execute the file + * using `drush script scratch.php`. That command will bootstrap your drupal + * site and then run the php below. + * + * The script command enables to store your script files wherever you wish and + * will help you list all of them should you collection grow. See its help. + * + */ + +// Just some ideas to get the juices flowing. +drush_print_r(user_roles()); +drush_print_r($GLOBALS['user']); diff --git a/sites/all/modules/drush/commands/core/search.drush.inc b/sites/all/modules/drush/commands/core/search.drush.inc new file mode 100644 index 00000000..8c283220 --- /dev/null +++ b/sites/all/modules/drush/commands/core/search.drush.inc @@ -0,0 +1,89 @@ +<?php +// $Id: search.drush.inc,v 1.6 2010/04/02 04:14:49 weitzman Exp $ + +function drush_core_search_status() { + list($remaining, $total) = _drush_core_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_core_search_status() { + $remaining = 0; + $total = 0; + if (drush_drupal_major_version() >= 7) { + foreach (module_implements('search_status') 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'); + $remaining += $status['remaining']; + $total += $status['total']; + } + } + return array($remaining, $total); +} + +function drush_core_search_index() { + drush_print(dt("Building the index may take a long time.")); + if (!drush_confirm(dt('Do you really want to continue?'))) { + return drush_set_error('CORE_SEARCH_REBUILD_ABORT', 'Aborting.'); + } + drush_op('_drush_core_search_index'); + drush_log(dt('The search index has been built.'), 'ok'); +} + +function _drush_core_search_index() { + list($remaining, ) = _drush_core_search_status(); + register_shutdown_function('search_update_totals'); + while ($remaining > 0) { + drush_log(dt('Remaining items to be indexed: ' . $remaining), 'ok'); + // Use drush_backend_invoke() to start subshell. Avoids out of memory issue. + $eval = "register_shutdown_function('search_update_totals');"; + if (drush_drupal_major_version() >= 7) { + foreach (variable_get('search_active_modules', array('node', 'user')) as $module) { + $eval .= " module_invoke($module, 'update_index');"; + } + } + else { + $eval .= " module_invoke_all('update_index');"; + } + drush_backend_invoke('php-eval', array($eval)); + list($remaining, ) = _drush_core_search_status(); + } +} + +function drush_core_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_set_error('CORE_SEARCH_REINDEX_ABORT', 'Aborting.'); + } + + if (drush_drupal_major_version() >= 7) { + drush_op('search_reindex'); + } + else { + drush_op('search_wipe'); + } + if (drush_get_option('immediate')) { + drush_op('_drush_core_search_index'); + drush_log(dt('The search index has been rebuilt.'), 'ok'); + } + else { + drush_log(dt('The search index will be rebuilt.'), 'ok'); + } +} diff --git a/sites/all/modules/drush/commands/core/site_install.drush.inc b/sites/all/modules/drush/commands/core/site_install.drush.inc new file mode 100644 index 00000000..f48bf9b3 --- /dev/null +++ b/sites/all/modules/drush/commands/core/site_install.drush.inc @@ -0,0 +1,134 @@ +<?php +// $Id: site_install.drush.inc,v 1.4 2010/06/21 18:55:08 weitzman Exp $ + +// Perform setup tasks for installation. +function drush_core_pre_site_install() { + + if (!$db_spec = drush_core_site_install_db_spec()) { + drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.')); + return; + } + + // TODO: not needed? + $sites_subdir = drush_get_option('sites-subdir', 'default'); + $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)); + } + $msg[] = dt("DROP your '@db' database and then CREATE a new one.", array('@db' => $db_spec['database'])); + + if (!drush_confirm(dt('You are about to ') . implode(dt(' and '), $msg) . ' Do you want to continue?')) { + return drush_set_error('CORE_SITE_INSTALL_ABORT', 'Aborting.'); + } + + // Can't install without sites directory and settings.php. + if (!file_exists($conf_path)) { + if (!drush_op('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 (!file_exists($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; + } + } + + + // Add a files dir if needed + if (!file_exists($files)) { + if (!drush_op('mkdir', $files) && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Failed to create directory @name', array('@name' => $files))); + return; + } + } + + // Now we can bootstrap up to the specified site if not already there. + drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE); + + // Drop and create DB if needed. + // TODO: support db-su like sql sync. + // Can't use drush_sql_query() since might not have a DB. + $exec = 'mysql ' . _drush_sql_get_credentials($db_spec); + // Strip DB name from credentials. Soon it won't exist anymore. We do + // need a DB name to connect to so use built-in mysql DB. + $replacement_db = 'information_schema'; + // Make sure we are only replacing the database name, + // and not a username or password that is the same as the database name. + $exec = str_replace(" {$db_spec['database']}", " {$replacement_db}", $exec) . ' -e '; + if (drush_op('system', $exec . ' "DROP DATABASE IF EXISTS ' . $db_spec['database'] . '"') && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Could not drop database: @name', array('@name' => $db_spec['database']))); + return; + } + + if (drush_op('system', $exec . '"CREATE DATABASE ' . $db_spec['database'] . '"') && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Could not create new database: @name', array('@name' => $db_spec['database']))); + return; + } +} + +function drush_core_site_install($profile = 'standard') { + define('MAINTENANCE_MODE', 'install'); + require_once DRUPAL_ROOT . '/includes/install.core.inc'; + + $db_spec = drush_core_site_install_db_spec(); + + $account_pass = drush_get_option('account-pass', 'admin'); + $settings = array( + 'parameters' => array( + 'profile' => $profile, + 'locale' => drush_get_option('locale', 'en'), + ), + 'forms' => array( + 'install_settings_form' => $db_spec, + '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' => drush_get_option('account-name', 'admin'), + '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), + ), + ), + ); + drush_log(dt('Starting Drupal installation. This takes 30 seconds or so ...'), 'ok'); + install_drupal($settings); +} + +// Return a db_spec based on supplied db_url/db_prefix options or +// an existing settings.php. +function drush_core_site_install_db_spec() { + if ($db_url = drush_get_option('db-url')) { + // We were passed a db_url. Usually a fresh site. + $db_spec = drush_convert_db_from_db_url($db_url); + $db_spec['db_prefix'] = drush_get_option('db-prefix'); + return $db_spec; + } + elseif (drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION)) { + // We have an existing settings.php. + $db_spec = _drush_sql_get_db_spec(); + $db_spec['db_prefix'] = $GLOBALS['db_prefix']; + return $db_spec; + } + else { + return FALSE; + } +} diff --git a/sites/all/modules/drush/commands/core/sitealias.drush.inc b/sites/all/modules/drush/commands/core/sitealias.drush.inc new file mode 100644 index 00000000..a7d2e9d2 --- /dev/null +++ b/sites/all/modules/drush/commands/core/sitealias.drush.inc @@ -0,0 +1,262 @@ +<?php +// $Id: sitealias.drush.inc,v 1.22 2010/04/22 17:57:40 weitzman Exp $ + +/** + * @file + * Site alias commands. @see example.drushrc.php for details. + */ + +function sitealias_drush_help($section) { + switch ($section) { + case 'drush:site-alias': + return dt('Print an alias record.'); + } +} + +function sitealias_drush_command() { + $items = array(); + + $items['site-alias'] = array( + 'callback' => '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 alias to print', + ), + 'options' => array( + '--full' => 'Print the full alias record for each site. Default when aliases are specified on the command line.', + '--short' => 'Print only the site alias name. Default when not 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.', + ), + '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\'.', + ), + ); + return $items; +} + +/** + * 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') . '/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(); + } + + return $site_list; +} + +/** + * Print out the specified site aliases using the format + * specified. + */ +function drush_sitealias_print() { + drush_bootstrap_max(); + + $site_list = _drush_sitealias_user_specified_list(); + $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(); + 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($full_output)) { + _drush_sitealias_print_record($alias_record, $site); + } + else { + drush_print($site); + } + } + drush_print_pipe(array_unique($site_specs)); +} + +/** + * 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 to go into the output + unset($alias_record['name']); + + // 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; +} diff --git a/sites/all/modules/drush/commands/core/upgrade.drush.inc b/sites/all/modules/drush/commands/core/upgrade.drush.inc new file mode 100644 index 00000000..720f3f51 --- /dev/null +++ b/sites/all/modules/drush/commands/core/upgrade.drush.inc @@ -0,0 +1,165 @@ +<?php +// $Id: upgrade.drush.inc,v 1.17 2010/04/29 19:05:37 weitzman Exp $ + +/* TODO + * - upgrade to specific releases. +/* + +/** + * @file + * Refine your Drupal major version upgrade. + */ + +/** + * Implement hook_drush_command(). + */ +function upgrade_drush_command() { + $items = array(); + + $items['site-upgrade'] = array( + 'description' => "Run a major version upgrade for Drupal core and contrib modules.", + 'drupal dependencies' => array('update'), + 'core' => array(6), // Remove once 3.0 is released. + 'arguments' => array( + 'target' => 'The name of a sitealias, which points to the destination site. root, uri, and db-url keys are required. See examples/aliases.drushrc.php for more information.'), + 'examples' => array( + 'drush site-upgrade @onward' => 'Upgrade from the current site to the site specified by @onward alias.' + ), + 'options' => array( + 'structure-tables-key' => 'A key in the structure-tables array. @see example.drushrc.php. Defaults to \'common\'.', + 'source-dump' => 'Path to dump file. Medium or large sized sites should set this. Optional; default is to create a temporary file.', + 'db-su' => 'DB username to use when dropping and creating the target database. Optional.', + 'db-su-pw' => 'DB password to use when dropping and creating the target database. Optional.', + ), + 'aliases' => array('sup'), + ); + return $items; +} + +/** + * Implement hook_drush_help(). + */ +function upgrade_drush_help($section) { + switch ($section) { + case 'drush:site-upgrade': + return dt("Execute a major version upgrade for Drupal core and enabled contrib modules. Command will download next version of Drupal and all available contrib modules that have releases (if not already downloaded). It prepares a settings.php for the target site, and copies the prior version's database to the target site. Finally, updatedb is run. The intent is for developers to keep re-running this command until they are satisfied with the resulting site."); + } +} + +function drush_upgrade_site_upgrade_validate($target_key = NULL) { + if (empty($target_key)) { + return drush_set_error(dt('Missing argument: target')); + } + + if (!$target_alias = drush_sitealias_get_record($target_key)) { + return drush_set_error('Site alias not found: @target-key. See example.drushrc.inc.', array('@target-key' => $target_key)); + } + + if (!file_exists(dirname($target_alias['root']))) { + drush_set_error('Site alias root not found: @root. See example.drushrc.inc.', array('@root' => dirname($target_alias['root']))); + } +} + +/** + * A drush command callback. + */ +function drush_upgrade_site_upgrade($target_key) { + $source_version = drush_drupal_major_version(); + $target_version = $source_version + 1; + $target_alias = drush_sitealias_get_record($target_key); + $destination_core = $target_alias['root']; + + // Fetch target core and place as per target alias root. + if (!file_exists($destination_core)) { + drush_set_option('destination', dirname($destination_core)); + drush_set_option('drupal-project-rename', basename($destination_core)); + + // No need for version control in this command. + drush_set_option('version-control', 'backup'); + + // TODO: get releases other than dev snapshot. + drush_pm_download('drupal-'. $target_version . '.x'); + if (drush_get_error()) return -1; // Early exit if we see an error. + } + + // Get enabled projects and their paths. + // TODO: D5 compatibility. + _update_cache_clear(); + module_load_include('inc', 'update', 'update.compare'); + $projects = update_get_projects(); + // We already downloaded Drupal project. + unset($projects['drupal']); + $projects = pm_get_project_path($projects, 'includes'); + + // Fetch and place each project into target. + // TODO: use non dev snapshot releases. + // TODO: Fix pm-download so this cancel hack is not needed. + drush_set_option('bootstrap_cancel', TRUE); + foreach ($projects as $key => $project) { + if (empty($project['path'])) { + $project['path'] = 'sites/all/modules'; + } + $destination_module = $destination_core . '/' . $project['path']; + if (!file_exists($destination_module)) { + drush_set_option('destination', dirname($destination_module)); + drush_pm_download($key . '-'. $target_version . '.x'); + } + } + if (drush_get_error()) return -1; // Early exit if we see an error. + + // Create sites subdirectory in target if needed. + $settings_source = conf_path() . '/settings.php'; + $settings_destination = $destination_core . '/' . $settings_source; + if (!file_exists(dirname($settings_destination))) { + if (!drush_op('mkdir', $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination))); + return; + } + } + + // Copy settings.php to target. + if (!file_exists($settings_destination)) { + if (!drush_op('copy', $settings_source, $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) { + drush_set_error(dt('Failed to copy @source to @dest', array('@source' => $settings_source, 'dest' => $settings_destination))); + return; + } + } + + // Append new $db_url with new DB name in target's settings.php. + drush_upgrade_fix_db_url($target_alias, $settings_destination); + + // Copy source database to target database. The source DB is not changed. + // Always set 'common' at minimum. Sites that want other can create other key in drushrc.php. + if (!drush_get_option('structure-tables-key')) { + drush_set_option('structure-tables-key', 'common'); + } + // Always blow away the target database so we start fresh. + drush_set_option('create-db', TRUE); + drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql'); + drush_sql_sync('@self', $target_key); + if (drush_get_error()) return -1; // Early exit if we see an error. + + // Run update.php in a subshell. It is run on @target site whereas this request was on @self. + drush_do_site_command($target_alias, 'updatedb', array(), array(), TRUE); +} + +// Replace db_url with DB name from target. updatedb will later append a DBTNG compatible version. +function drush_upgrade_fix_db_url($target_alias, $settings_destination) { + $old_url = $GLOBALS['db_url']; + if (is_array($old_url)) { + $old_url = $old_url['default']; + } + $target_alias_databases = sitealias_get_databases_from_record($target_alias); + $new_url = substr($old_url, 0, strrpos(trim($old_url), '/')) . '/'. $target_alias_databases['default']['default']['database']; + + $append = "\n# Added by drush site-upgrade."; + if (drush_drupal_major_version() <= 6) { + $append .= "\n" . '$db_url = \'' . $new_url . '\';'; + } + else { + $databases = $GLOBALS['databases']; + $databases['default']['default']['database'] = $target_alias_databases['default']['default']['database']; + $append .= "\n" . '$databases = ' . var_export($databases, TRUE) . ';'; + } + drush_op('file_put_contents', $settings_destination, $append, FILE_APPEND); +} diff --git a/sites/all/modules/drush/commands/core/variable.drush.inc b/sites/all/modules/drush/commands/core/variable.drush.inc new file mode 100644 index 00000000..5c2647e1 --- /dev/null +++ b/sites/all/modules/drush/commands/core/variable.drush.inc @@ -0,0 +1,205 @@ +<?php +// $Id: variable.drush.inc,v 1.19 2010/03/25 02:48:17 weitzman Exp $ + +/** + * Implementation of hook_drush_help(). + * + * This function is called whenever a drush user calls + * 'drush help <name-of-your-command>' + * + * @param + * A string with the help section (prepend with 'drush:') + * + * @return + * A string with the help text for your command. + */ +function variable_drush_help($section) { + switch ($section) { + case 'drush:variable-get': + return dt("Lists the variables for your site"); + case 'drush:variable-set': + return dt("Set a variable"); + case 'drush:variable-delete': + return dt("Delete a variable."); + } +} + + +/** + * Implementation of hook_drush_command(). + * + * In this hook, you specify which commands your + * drush module makes available, what it does and + * description. + * + * Notice how this structure closely resembles how + * you define menu hooks. + * + * @return + * An associative array describing your command(s). + */ +function variable_drush_command() { + $items['variable-get'] = array( + 'description' => 'Get a list of some or all site variables and values.', + 'arguments' => array( + 'name' => 'A string to filter the variables by. Only variables beginning with the string will be listed.', + ), + 'examples' => array( + 'drush vget' => 'List all variables and values.', + 'drush vget user' => 'List all variables beginning with the string "user".', + ), + 'options' => array( + '--pipe' => 'Use var_export() to emit executable PHP. Useful for pasting into code.', + ), + '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.', + ), + 'options' => array( + '--yes' => 'Skip confirmation if only one variable name matches.', + '--always-set' => 'Always skip confirmation.', + ), + 'examples' => array( + 'drush vset --yes preprocess_css 1' => 'Set the preprocess_css variable to true. Skip confirmation if variable already exists.', + 'drush vset --always-set site_offline 1' => 'Take the site offline; Skips confirmation even if site_offline variable does not exist.', + 'drush vset pr 1' => 'Choose from a list of variables beginning with "pr" to set to true.', + ), + '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.', + ), + 'options' => array( + '--yes' => 'Skip confirmation if only one variable name matches.', + ), + '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.', + ), + 'aliases' => array('vdel'), + ); + + return $items; +} + +/** + * Command callback. + * List your site's variables. + */ +function drush_variable_get() { + global $conf; + $keys = array_keys($conf); + if ($args = func_get_args()) { + $keys = preg_grep("/{$args[0]}/", $keys); + } + foreach ($keys as $name) { + $value = $conf[$name]; + drush_print_pipe("\$variables['$name'] = ". var_export($value, TRUE). ";\n"); + + if ($value === TRUE) { + $value = 'TRUE'; + } + elseif ($value === FALSE) { + $value = 'FALSE'; + } + elseif (is_string($value)) { + $value = '"' . $value . '"'; + } + elseif (is_array($value) || is_object($value)) { + $value = print_r($value, TRUE); + } + drush_print($name . ': ' . $value); + } +} + +/** + * Command callback. + * Set a variable. + */ +function drush_variable_set() { + $args = func_get_args(); + if (!isset($args[0])) { + return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No variable specified.')); + } + $value = $args[1]; + if (!isset($value)) { + return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.')); + } + + $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 ((drush_get_option('always-set', FALSE)) || ((count($options) == 1 || $match) && drush_get_context('DRUSH_AFFIRMATIVE'))) { + variable_set($args[0], $value); + drush_log(dt('!name was set to !value.', array('!name' => $args[0], '!value' => $value)), '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' => $value)), 'success'); + } + } +} + +/** + * Command callback. + * Delete a variable. + */ +function drush_variable_delete() { + $args = func_get_args(); + if (!isset($args[0])) { + drush_set_error('DRUSH_VARIABLE_ERROR', dt('No variable specified')); + } + // Look for similar variable names. + $result = drush_variable_like($args[0]); + + $options = array(); + while ($name = drush_db_result($result)) { + $options[] = $name; + } + + 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'); +} diff --git a/sites/all/modules/drush/commands/core/watchdog.drush.inc b/sites/all/modules/drush/commands/core/watchdog.drush.inc new file mode 100644 index 00000000..4d5ddb21 --- /dev/null +++ b/sites/all/modules/drush/commands/core/watchdog.drush.inc @@ -0,0 +1,374 @@ +<?php + +/** + * Implementation of hook_drush_help(). + */ +function watchdog_drush_help($section) { + switch ($section) { + case 'drush:watchdog-list': + return dt('Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.'); + case 'drush:watchdog-show': + return dt('Show watchdog messages. Arguments and options can be combined to configure which messages to show.'); + case 'drush:watchdog-delete': + return dt('Delete watchdog messages. Arguments or options must be provided to specify which messages to delete.'); + } +} + +/** + * Implementation of hook_drush_command(). + */ +function watchdog_drush_command() { + $items['watchdog-list'] = array( + 'description' => 'Show available message types and severity levels. A prompt will ask for a choice to show watchdog messages.', + 'drupal dependencies' => drush_drupal_major_version() >= 6 ? array('dblog') : array('watchdog'), + 'aliases' => array('wd-list'), + ); + $items['watchdog-show'] = array( + 'description' => 'Show watchdog messages.', + 'drupal dependencies' => drush_drupal_major_version() >= 6 ? array('dblog') : array('watchdog'), + '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.' + ), + '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' => 'Show a listing of most recent 10 messages 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' => drush_drupal_major_version() >= 6 ? array('dblog') : array('watchdog'), + 'arguments' => 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'), + 'deprecated-aliases' => array('wd'), + ); + 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_log(dt('Aborting.')); + } + $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); + + $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.')); + } + $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); + $table[] = array($row->wid, $row->date, $row->severity, $row->type, $row->message); + } + if (empty($table)) { + 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) { + // We will 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. + $last = array_pop($table); + $last_wid = $last[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); + $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. + * @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. + if (drush_drupal_major_version() > 5) { + $variables = unserialize($result->variables); + if (is_array($variables)) { + $result->message = strtr($result->message, $variables); + } + unset($result->variables); + } + $result->message = truncate_utf8(strip_tags(decode_entities($result->message)), 188, FALSE, FALSE); + + // This data is only used in a detailed view. + 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); + } + + 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_log(dt('Aborting.')); + } + $affected_rows = drush_db_delete('watchdog'); + drush_log(dt('!affected watchdog messages have been deleted.', array('!affected' => $affected_rows)), '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_log(dt('Aborting.')); + } + $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, $filter, 'OR'); + if ($where === FALSE) { + return drush_log(dt('Aborting.'), 'error'); + } + drush_print(dt('All messages matching "!where" will be deleted.', array('!where' => strtr($where['where'], $where['args'])))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_log(dt('Aborting.')); + } + $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 = "Unknown message type: !type.\nValid types are: !types."; + return drush_set_error(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() { + if (drush_drupal_major_version() == 5) { + return _watchdog_get_message_types(); + } + else { + return _dblog_get_message_types(); + } +} diff --git a/sites/all/modules/drush/commands/pm/package_handler/cvs.inc b/sites/all/modules/drush/commands/pm/package_handler/cvs.inc new file mode 100644 index 00000000..8102c0eb --- /dev/null +++ b/sites/all/modules/drush/commands/pm/package_handler/cvs.inc @@ -0,0 +1,125 @@ +<?php +// $Id: cvs.inc,v 1.15 2010/02/25 01:54:15 weitzman Exp $ + +/** + * @file Drush PM CVS extension + */ + +/** + * Install a project. + * + * @param $project The project array with name, base and full (final) paths. + * @param $release The release details array from drupal.org + */ +function package_handler_install_project($project, $release) { + drush_log('Downloading project ' . $project['name'] . ' ...'); + + // Check it out. + drush_pm_cvs($project, $release); + + if (!drush_get_context('DRUSH_SIMULATE')) { + if (is_dir($project['full_project_path'])) { + drush_log("Checking out " . $project['name'] . " was successful."); + return TRUE; + } + else { + drush_set_error('DRUSH_PM_CVS_CHECKOUT_PROBLEMS', dt("Unable to check out !project to !destination from cvs.drupal.org", array('!project' => $project['name'], '!destination' => $project['full_project_path']))); + return FALSE; + } + } +} + +/** + * Update a project (so far, only modules are supported). + * + * @param $project The project array with name, base and full (final) paths. + * @param $release The release details array from drupal.org + */ +function package_handler_update_project($project, $release) { + drush_log('Updating project ' . $project['name'] . ' ...'); + + + // Check out a fresh copy, or update an existing one. + drush_pm_cvs($project, $release); + + if (is_dir($project['full_project_path']) && !drush_get_context('DRUSH_SIMULATE')) { + drush_log("Updating of " . $project['name'] . " was successful."); + return TRUE; + } + else { + return drush_set_error('DRUSH_PM_CVS_UPDATE_PROBLEMS', dt("Unable to update !project from cvs.drupal.org", array('!project' => $project['name']))); + } +} + +/** + * General CVS helper function + * @param $project The project array with name, base and full (final) paths. + * @param $release The release details array from drupal.org + */ +function drush_pm_cvs($project, $release) { + // By default we assume a module + $repos = 'drupal-contrib'; + $cvsdir = 'contributions/modules/'; + + switch ($project['project_type']) { + case 'theme': + $cvsdir = 'contributions/themes/'; + break; + case 'theme engine': + $cvsdir = 'contributions/theme-engines/'; + break; + case 'translation': + drush_set_error('DRUSH_PM_CVS_NO_TRANSLATIONS', 'You cannot install translations with CVS, because they require processing (normally done on drupal.org to produce the tar.gz files).'); + exit(); + case 'profile': + $cvsdir = 'contributions/profiles/'; + break; + case 'core': + $cvsdir = ''; + $repos = 'drupal'; + } + + $cvsmethod = drush_get_option('cvsmethod', FALSE); + $cvsparams = drush_get_option('cvsparams', FALSE); + // Determine reasonable defaults, based on context. + if (empty($cvsmethod)) { + $cvsmethod = 'checkout'; + // If we have an existing working copy we update. + if (is_dir($project['full_project_path'] . '/CVS')) { + $cvsmethod = 'update'; + // By default we update overwriting changes, however if we have + // an existing CVS checkout that is version controlled then the + // default is to update in place, which will attempt to merge changes + // but we assume anyone using SVN is competent enough to deal with this! + // TODO: Make this work with BZR etc. + if (!file_exists($path . '/.svn') && $cvsparams === FALSE) { + $cvsparams = '-dPC'; + } + } + } + if ($cvsparams === FALSE) { + $cvsparams = '-dP'; + } + + // CVS only accepts relative paths. We will cd in the checkout path right + // before running the cvs command. + $checkout_path = $project['base_project_path']; + + $cvsparts = array(); + $cvsparts[] = '-z6'; + $cvsparts[] = $cvsparams; + $cvsparts[] = '-d:pserver:' . drush_get_option('cvscredentials', 'anonymous:anonymous') . '@cvs.drupal.org:/cvs/' . $repos; + $cvsparts[] = $cvsmethod; + $cvsparts[] = '-r '. $release['tag']; + if ($cvsmethod == 'checkout') { + $cvsparts[] = '-d ' . $project['name']; + $cvsparts[] = $cvsdir . $project['name']; + } + else { + $cvsparts[] = $project['name']; + } + + if (!drush_shell_exec('cd ' . $checkout_path . ' ; cvs ' . implode(' ', $cvsparts))) { + drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to checkout ' . $project['name'] . ' from cvs.drupal.org.'); + } +} diff --git a/sites/all/modules/drush/commands/pm/package_handler/wget.inc b/sites/all/modules/drush/commands/pm/package_handler/wget.inc new file mode 100644 index 00000000..89f7a73a --- /dev/null +++ b/sites/all/modules/drush/commands/pm/package_handler/wget.inc @@ -0,0 +1,103 @@ +<?php +// $Id: wget.inc,v 1.14 2010/04/27 00:32:13 greg1anderson Exp $ + +/** + * @file Drush PM Wget extension + */ + +/** + * Install a project. + * + * @param $project The project array with name, base and full (final) paths. + * @param $release The release details array from drupal.org + */ +function package_handler_install_project(&$project, $release) { + drush_log('Downloading project ' . $project['name'] . ' ...'); + + // Get the filename... + $filename = explode('/', $release['download_link']); + $filename = array_pop($filename); + + // Set our directory to the download location. + $olddir = getcwd(); + chdir($project['base_project_path']); + + // Download it. + if (!drush_shell_exec("wget -P . " . $release['download_link'])) { + drush_shell_exec("curl -O " . $release['download_link']); + } + + if (file_exists($filename) || drush_get_context('DRUSH_SIMULATE')) { + drush_log("Downloading " . $filename . " was successful."); + } + else { + chdir($olddir); + return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $project['base_project_path'] . ' from '. $release['download_link']); + } + + // Check Md5 hash + if (md5_file($filename) != $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) { + drush_op('unlink', $filename); + chdir($olddir); + return drush_set_error('DRUSH_PM_FILE_CORRUPT', "File $filename is corrupt (wrong md5 checksum)."); + } + else { + drush_log("Md5 checksum of $filename verified."); + } + + // Decompress + drush_shell_exec("gzip -d " . $filename); + $tarpath = basename($filename, '.tar.gz'); + $tarpath = basename($tarpath, '.tgz'); + $tarpath .= '.tar'; + + // Untar + drush_shell_exec("tar -xf $tarpath"); + // We're not using tar -xzf because that's not working on windows... + + // Handle the dodgy directory used by the drupal project' packaging script. + if ($project['name'] == 'drupal') { + // The dodgy directory name is always the first line of output. + drush_shell_exec("tar -tf $tarpath"); + $output = drush_shell_exec_output(); + $project_destination_name = $output[0]; + + if ($rename = drush_get_option($project['name'] . '-project-rename')) { + // If someone specifies --drupal-project-rename, but does not + // provide a name, then default to 'drupal'. + if ($rename === TRUE) { + $rename = $project['name']; + } + $rename = trim($rename, '/'); + $renamed_destination = $project['base_project_path'] . (substr($project['base_project_path'], -1) != '/' ? '/' : '') . $rename; + + $rename_result = drush_op('rename', $project['base_project_path'] . $project_destination_name, $renamed_destination); + if ($rename_result) { + $project_destination_name = $rename; + } + } + $project['full_project_path'] = $project['base_project_path'] . (substr($project['base_project_path'], -1) != '/' ? '/' : '') . $project_destination_name; + } + + // Remove the tarball + drush_op('unlink', $tarpath); + + // Set working directory back to the previous working directory. + chdir($olddir); + + // 'drupal' project and profiles get untarred as drupal-6-10/foo instead of drupal/foo so must be excluded from test below. + // Translations get untarred into the Drupal root, so there is no easy way to check. + if (!is_dir($project['full_project_path']) && !drush_get_context('DRUSH_SIMULATE') && $project['name'] != 'drupal' && !in_array($release['type'], array('translation', 'profile'))) { + return drush_set_error('DRUSH_PM_FILE_UNTAR_ERROR', 'Downloaded file ' . $filename . ' couldn\'t be untarred to ' . $project['full_project_path'] . ' correctly'); + } + else { + return TRUE; + } +} + +/** + * This is an alias of the install function, since they are identical + */ +function package_handler_update_project(&$project, $release) { + return package_handler_install_project($project, $release); +} diff --git a/sites/all/modules/drush/commands/pm/pm.drush.inc b/sites/all/modules/drush/commands/pm/pm.drush.inc new file mode 100644 index 00000000..c7627862 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/pm.drush.inc @@ -0,0 +1,1475 @@ +<?php +// $Id: pm.drush.inc,v 1.116 2010/06/16 15:08:02 weitzman Exp $ + +/** + * @file + * The drush Package Manager + * + * Terminology: + * - Request: a requested package (string or keyed array), with a project name and (optionally) version. + * - Project: a drupal.org project, such as cck or zen. + * - Version: a requested version, such as 1.0 or 1.x-dev. + * - Release: a specific release of a project, with associated metadata (from the drupal.org update service). + * - Package: the collection of files that make up a release. + */ + +/** + * Project is a user requested version update. + */ +define('DRUSH_PM_REQUESTED_UPDATE', 101); + +/** + * User requested version already installed. + */ +define('DRUSH_PM_REQUESTED_CURRENT', 102); + +/** + * User requested version already installed. + */ +define('DRUSH_PM_NO_VERSION', 103); + +/** + * User requested version not found. + */ +define('DRUSH_PM_REQUESTED_NOT_FOUND', 104); + +/** + * Sort callback function for sorting projects + * First by type, second by package and third by name + * + * Special handling for 'Package' on modules and themes. + */ +function _drush_pm_sort_projects($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; +} + +/** + * Implementation of hook_drush_help(). + */ +function pm_drush_help($section) { + switch ($section) { + case 'drush:pm-enable': + return dt('Enable one or more modules or themes. Enable dependant modules as well.'); + case 'drush:pm-disable': + return dt('Disable one or more modules or themes. Disable dependant modules as well.'); + case 'drush:pm-info': + return dt('Show detailed info for one or more projects.'); + case 'drush:pm-uninstall': + return dt('Uninstall one or more modules. Modules must be disabled first.'); + case 'drush:pm-list': + return dt('Show a list of available modules and themes.'); + case 'drush:pm-refresh': + return dt('Refresh update status information. Run this before running update or updatecode commands.'); + case 'drush:pm-updatecode': + return dt("Display available update information and allow updating of all installed project code to the specified version (or latest by default). Note: The user is asked to confirm before the actual update. Use the --yes option to answer yes automatically."); + case 'drush:pm-update': + return dt("Display available update information and allow updating of all installed projects to the specified version (or latest by default), followed by applying any database updates required (as with running update.php). Note: The user is asked to confirm before the actual update. Use the --yes option to answer yes automatically."); + case 'drush:pm-releases': + return dt("View all releases for a given project (modules, themes, profiles, translations). Useful for deciding which version to install/update."); + case 'drush:pm-download': + return dt("Quickly download projects (modules, themes, profiles, translations) from drupal.org. Automatically figures out which module version you want based on its latest release, or you may specify a particular version. Downloads drupal core as well. If no destination is provided, defaults to a site specific modules directory if available, then to sites/all/modules if available, then to the current working directory."); + } +} + +/** + * Implementation of hook_drush_command(). + */ +function pm_drush_command() { + $update = 'update'; + if (drush_drupal_major_version() == 5) { + $update = 'update_status'; + } + $engines = array( + 'engines' => array( + 'version_control' => 'Integration with VCS in order to easily commit your changes to projects.', + 'package_handler' => 'Determine how to download/checkout new projects and acquire updates to projects.', + ), + ); + + $items['pm-enable'] = array( + 'description' => 'Enable one or more modules or themes.', + 'arguments' => array( + 'modules' => 'A space delimited list of modules or themes. You can use the * wildcard at the end of module and theme names to to enable all matches.', + ), + 'aliases' => array('en'), + 'deprecated-aliases' => array('enable'), + ); + $items['pm-disable'] = array( + 'description' => 'Disable one or more modules or themes.', + 'arguments' => array( + 'modules' => 'A space delimited list of modules or themes. You can use the * wildcard at the end of module and theme names to disable multiple matches.', + ), + 'aliases' => array('dis'), + 'deprecated-aliases' => array('disable'), + ); + $items['pm-info'] = array( + 'description' => 'Show info for one or more projects.', + 'arguments' => array( + 'projects' => 'A space delimited list of projects. You can use the * wildcard at the end of module names to get info for the project and all its sub projects.', + ), + ); + // 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 space delimited list of modules.', + ), + 'deprecated-aliases' => array('uninstall'), + ); + $items['pm-list'] = array( + 'description' => 'Show a list of available modules and themes', + 'callback arguments' => array(array(), FALSE), + 'options' => array( + '--type' => 'Filter by project type. Choices: module, theme.', + '--status' => 'Filter by project status. Choices: enabled,disable and/or \'not installed\'. You can use multiple comma separated values. (i.e. --status="disabled,not installed").', + '--package' => 'Filter by project packages. You can use multiple comma separated values. (i.e. --package="Core - required,Other").', + '--pipe' => 'Returns a space delimited list of the names of the resulting projects.', + ), + 'aliases' => array('sm'), + 'deprecated-aliases' => array('statusmodules'), + ); + $items['pm-refresh'] = array( + 'description' => 'Refresh update status information', + 'drupal dependencies' => array($update), + 'aliases' => array('rf'), + 'deprecated-aliases' => array('refresh'), + ); + $items['pm-updatecode'] = array( + 'description' => 'Update your project code', + 'drupal dependencies' => array($update), + 'arguments' => array( + 'projects' => 'Optional. A space delimited list of installed projects (modules or themes) to update.', + ), + 'options' => array( + '--backup-dir' => 'Specify a directory to backup packages into, defaults to a backup directory within your Drupal root.', + '--pipe' => 'Returns a space delimited list of enabled modules and their respective version and update information, one module per line. Order: module name, current version, recommended version, update status.', + ), + 'aliases' => array('upc'), + 'deprecated-aliases' => array('updatecode'), + ) + $engines; + $items['pm-update'] = array( + 'description' => 'Update your project code and apply any database updates required (update.php)', + 'drupal dependencies' => array($update), + 'arguments' => array( + 'projects' => 'Optional. A space delimited list of installed projects (modules or themes) to update.', + ), + 'options' => array( + '--backup-dir' => 'Specify a directory to backup packages into, defaults to a backup directory within your Drupal root.', + '--php' => 'Specify absolute path to you php binary. Use this if you see errors.' + ), + 'aliases' => array('up'), + 'deprecated-aliases' => array('update'), + ); + $items['pm-releases'] = array( + 'description' => 'Release information for a project', + 'drupal dependencies' => array($update), + 'arguments' => array( + 'projects' => 'A space separated list of drupal.org project names.', + ), + 'examples' => array( + 'drush pm-releases cck zen' => 'View releases for cck and Zen projects.', + ), + 'deprecated-aliases' => array('info'), + ); + $items['pm-download'] = array( + 'description' => 'Download core Drupal and projects like CCK, Zen, etc.', + 'examples' => array( + 'drush dl' => 'Download latest version of Drupal core.', + 'drush dl drupal' => 'Download latest stable version of Drupal core', + 'drush dl drupal-7.x' => 'Download latest 7.x development version of Drupal core', + 'drush dl cck zen es' => 'Download latest versions of CCK, Zen and Spanish translations for my version of Drupal.', + '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.', + ), + 'arguments' => array( + 'projects' => 'A space separated list of project names, with optional version. Defaults to \'drupal\'', + ), + 'options' => array( + '--destination' => 'Path to which the project will be copied.', + '--source' => 'The base URL which provides project release history in XML. Defaults to http://updates.drupal.org/release-history.', + '--variant' => "Only useful for install profiles. Possible values: 'core', 'no-core', 'make'.", + '--drupal-project-rename' => 'Alternate name for "drupal" directory when downloading drupal project.', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all. + 'aliases' => array('dl'), + 'deprecated-aliases' => array('download'), + ) + $engines; + return $items; +} + +/** + * Command callback. Show a list of modules 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; + + $projects = drush_pm_get_projects(); + uasort($projects, '_drush_pm_sort_projects'); + + $major_version = drush_drupal_major_version(); + foreach ($projects as $project) { + if (!in_array($project->type, $type_filter)) { + continue; + } + $status = drush_get_project_status($project); + if (!in_array($status, $status_filter)) { + continue; + } + if (($major_version >= 7) and (isset($project->info['hidden']))) { + continue; + } + + // filter by package + if (!empty($package_filter)) { + if (!in_array(strtolower($project->info['package']), $package_filter)) { + continue; + } + } + + if (empty($package_filter) || count($package_filter) > 1) { + $row[] = $project->info['package']; + } + + if (($major_version >= 6)||($project->type == 'module')) { + $row[] = $project->info['name'].' ('.$project->name.')'; + } + else { + $row[] = $project->name; + } + if (count($type_filter) > 1) { + $row[] = ucfirst($project->type); + } + if (count($status_filter) > 1) { + $row[] = ucfirst($status); + } + if (($major_version >= 6)||($project->type == 'module')) { + $row[] = $project->info['version']; + } + + $rows[] = $row; + $pipe[] = $project->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); + } +} + +/** + * Command callback. Enable one or more projects. + */ +function drush_pm_enable() { + $args = func_get_args(); + + $project_info = drush_get_projects(); + + // Classify $args in themes, modules or unknown. + $modules = array(); + $themes = array(); + drush_pm_classify_projects($args, $modules, $themes, $project_info); + $projects = array_merge($modules, $themes); + $unknown = array_diff($args, $projects); + + // Discard and set an error for each unknown project. + foreach ($unknown as $project) { + drush_set_error('DRUSH_PM_ENABLE_PROJECT_NOT_FOUND', dt('!project was not found and will not be enabled.', array('!project' => $project))); + } + + // Discard already enabled projects. + foreach ($projects as $project) { + if ($project_info[$project]->status) { + if ($project_info[$project]->type == 'module') { + unset($modules[$project]); + } + else { + unset($themes[$project]); + } + drush_log(dt('!project is already enabled.', array('!project' => $project)), 'ok'); + } + } + + if (!empty($modules)) { + // Check module dependencies. + $dependencies = drush_check_module_dependencies($modules, $project_info); + $all_dependencies = array(); + foreach ($dependencies as $key => $info) { + if (isset($info['error'])) { + unset($modules[$key]); + 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); + } + } + $all_dependencies = array_unique($all_dependencies); + $enabled = array_keys(array_filter(drush_get_modules(), 'pm_is_enabled')); + $needed = array_diff($all_dependencies, $enabled); + $modules = $needed + $modules; + + // Discard modules which don't meet requirements. + require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/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 don\'t meet the requirements to be enabled.', array('!module' => $module))); + } + } + } + + // Inform the user which projects will finally be enabled. + $projects = array_merge($modules, $themes); + if (empty($projects)) { + return drush_log(dt('There were no projects that could be enabled.'), 'ok'); + } + else { + drush_print(dt('The following projects will be enabled: !projects', array('!projects' => implode(', ', $projects)))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_log(dt('Aborting.')); + } + } + + // 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); + $current = drupal_map_assoc($enabled, 'pm_true'); + $processed = drupal_map_assoc($modules, 'pm_true'); + $active_modules = array_merge($current, $processed); + drush_system_modules_form_submit($active_modules); + } + + // Inform the user of final status. + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:projects)', array(':projects' => $projects)); + while ($project = drush_db_fetch_object($rsc)) { + if ($project->status) { + drush_log(dt('!project was enabled successfully.', array('!project' => $project->name)), 'ok'); + } + else { + drush_set_error('DRUSH_PM_ENABLE_PROJECT_ISSUE', dt('There was a problem enabling !project.', array('!project' => $project->name))); + } + } +} + +/** + * Command callback. Disable one or more projects. + */ +function drush_pm_disable() { + $args = func_get_args(); + + $project_info = drush_get_projects(); + + // classify $args in themes, modules or unknown + $modules = array(); + $themes = array(); + drush_pm_classify_projects($args, $modules, $themes, $project_info); + $projects = array_merge($modules, $themes); + $unknown = array_diff($args, $projects); + + // Discard and set an error for each unknown project. + foreach ($unknown as $project) { + drush_set_error('DRUSH_PM_ENABLE_PROJECT_NOT_FOUND', dt('!project was not found and will not be disabled.', array('!project' => $project))); + } + + // Discard already disabled projects. + foreach ($projects as $project) { + if (!$project_info[$project]->status) { + if ($project_info[$project]->type == 'module') { + unset($modules[$project]); + } + else { + unset($themes[$project]); + } + drush_log(dt('!project is already disabled.', array('!project' => $project)), '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'); + } + } + + // Add enabled dependents to list of modules to disable. + if (!empty($modules)) { + $enabled = array_keys(array_filter(drush_get_modules(), 'pm_is_enabled')); + $dependents = drush_module_dependents($modules, $project_info); + $dependents = array_unique($dependents); + $dependents = array_intersect($dependents, $enabled); + $modules = array_merge($modules, $dependents); + } + + // Inform the user which projects will finally be disabled. + $projects = array_merge($modules, $themes); + if (empty($projects)) { + return drush_log(dt('There were no projects that could be disabled.'), 'ok'); + } + else { + drush_print(dt('The following projects will be disabled: !projects', array('!projects' => implode(', ', $projects)))); + if(!drush_confirm(dt('Do you really want to continue?'))) { + return drush_log(dt('Aborting.')); + } + } + + // 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); + $active_modules = array_diff($enabled, $modules); + $active_modules = drupal_map_assoc($active_modules, 'pm_true'); + drush_system_modules_form_submit($active_modules); + } + + // Inform the user of final status. + $rsc = drush_db_select('system', array('name', 'status'), 'name IN (:projects)', array(':projects' => $projects)); + while ($project = drush_db_fetch_object($rsc)) { + if (!$project->status) { + drush_log(dt('!project was disabled successfully.', array('!project' => $project->name)), 'ok'); + } + else { + drush_set_error('DRUSH_PM_DISABLE_PROJECT_ISSUE', dt('There was a problem disabling !project.', array('!project' => $project->name))); + } + } +} + +/** + * Wrapper of drupal_get_projects() with additional information used by + * pm- commands. + * + * @return + * An array containing info for all available modules and themes w/additional + * info. + */ +function drush_pm_get_projects() { + $projects = drush_get_projects(); + foreach ($projects as $key => $project) { + if (empty($project->info['package'])) { + $projects[$key]->info['package'] = dt('Other'); + } + } + return $projects; +} + +/** + * Classify projects in modules, themes or unknown ones. + * + * @param $projects + * Array of project names, by reference. + * @param $modules + * Empty array to be filled with modules in $projects. + * @param $themes + * Empty array to be filled with themes in $projects. + */ +function drush_pm_classify_projects(&$projects, &$modules, &$themes, $project_info) { + _drush_pm_expand_projects($projects, $project_info); + foreach ($projects as $project) { + if (!isset($project_info[$project])) { + continue; + } + if ($project_info[$project]->type == 'module') { + $modules[$project] = $project; + } + else if ($project_info[$project]->type == 'theme') { + $themes[$project] = $project; + } + } +} + +/** + * Command callback. Show detailed info for one or more projects. + */ +function drush_pm_info() { + $args = func_get_args(); + + $project_info = drush_pm_get_projects(); + _drush_pm_expand_projects($args, $project_info); + + foreach ($args as $project) { + if (isset($project_info[$project])) { + $info = $project_info[$project]; + } + else { + drush_set_error('DRUSH_PM_INFO_PROJECT_NOT_FOUND', dt('!project was not found.', array('!project' => $project))); + 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 a string with general info of a project (module or theme). + */ +function _drush_pm_info_project($info) { + $major_version = drush_drupal_major_version(); + + $data['Project'] = $info->name; + $data['Type'] = $info->type; + if (($info->type == 'module')||($major_version >= 6)) { + $data['Title'] = $info->info['name']; + $data['Description'] = $info->info['description']; + $data['Version'] = $info->info['version']; + } + $data['Package'] = $info->info['package']; + if ($major_version >= 6) { + $data['Core'] = $info->info['core']; + } + if ($major_version == 6) { + $data['PHP'] = $info->info['php']; + } + $data['Status'] = drush_get_project_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 a string with info of a module. + */ +function _drush_pm_info_module($info) { + $major_version = drush_drupal_major_version(); + + $data = _drush_pm_info_project($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) { + if (count($info->info['dependents']) > 0) { + $requiredby = implode(', ', $info->info['dependents']); + } + else { + $requiredby = "none"; + } + $data['Required by'] = $requiredby; + } + + return $data; +} + +/** + * Return a string with info of a theme. + */ +function _drush_pm_info_theme($info) { + $major_version = drush_drupal_major_version(); + + $data = _drush_pm_info_project($info); + if ($major_version == 5) { + $data['Engine'] = $info->description; + } + else { + $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; +} + +/** + * Add sub projects that match project_name*. + * + * A helper function for commands that take a space separated list of project + * names. It will identify project names that have been passed in with a + * trailing * and add all matching projects to the array that is returned. + * + * @param $projects + * An array of projects, by reference. + * @param $project_info + * Optional. An array of project info as returned by drush_get_projects(). + */ +function _drush_pm_expand_projects(&$projects, $project_info = array()) { + if (empty($project_info)) { + $project_info = drush_get_projects(); + } + foreach ($projects as $key => $project) { + if (($wildcard = rtrim($project, '*')) !== $project) { + foreach (array_keys($project_info) as $project_name) { + if (strpos($project_name, $wildcard) !== FALSE) { + $projects[] = $project_name; + } + } + unset($projects[$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 = func_get_args(); + + drush_include_engine('drupal', 'environment'); + $module_info = drush_get_modules(); + + // 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_set_error('DRUSH_PM_ENABLE_MODULE_NOT_FOUND', dt('Module !module was not found and will not be uninstalled.', array('!module' => $module))); + } + else if ($module_info[$module]->status) { + // The module is enabled. + unset($modules[$key]); + drush_set_error('DRUSH_PM_UNINSTALL_ACTIVE_MODULE', dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module))); + } + 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'); + } + } + + // 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_log(dt('Aborting.')); + } + } + + // 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'); + } +} + +/** + * Array filter callback to return enabled modules. + * + * @param $module + * A module object as returned by drush_get_modules(). + */ +function pm_is_enabled($module) { + return $module->status; +} + +/** + * Callback helper. + */ +function pm_true() { + return TRUE; +} + +/** + * We need to set the project path by looking at the module location. Ideally, update.module would do this for us. + * + * TODO: Improve logic so this works even if your project directory is not + * named the same as the project name. + */ +function pm_get_project_path($projects, $lookup) { + foreach ($projects as $name => $project) { + if (!isset($project['path']) && $name != 'drupal') { + // looks for an enabled module. + foreach ($project[$lookup] as $filename => $title) { + if ($path = drupal_get_path($project['project_type'], $filename)) { + continue; + } + } + // As some modules are not located in their project's root directory + // but in a subdirectory (e.g. all the ecommerce modules), we take the module's + // info file's path, and then move up until we are at a directory with the + // project's name. + $parts = explode('/', $path); + $i = count($parts) - 1; + $stop = array_search($name, $parts); + while ($i > $stop) { + unset($parts[$i]); + $i--; + } + $projects[$name]['path'] = implode('/', $parts); + } + } + return $projects; +} + +/** + * A drush command callback. Show release info for given project(s). + * + **/ +function drush_pm_releases() { + // 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'); + + $projects = func_get_args(); + $projects = drupal_map_assoc($projects); + $info = pm_get_project_info($projects); + $project_info = drush_get_projects(); + + $rows[] = array(dt('Project'), dt('Release'), dt('Date'), dt('Status')); + foreach ($info as $key => $project) { + $recommended = isset($project['recommended_major'])?$project['recommended_major']:NULL; + $supported = isset($project['supported_majors'])?explode(',', $project['supported_majors']):array(); + $default = $project['default_major']; + $recommended_version = NULL; + $latest_version = NULL; + foreach ($project['releases'] as $release) { + if ($release['version_major'] == $recommended) { + if (!isset($latest_version)) { + $latest_version = $release['version']; + } + if (empty($release['version_extra'])) { + if (!isset($recommended_version)) { + $recommended_version = $release['version']; + } + } + } + } + if (!isset($recommended_version)) { + $recommended_version = $latest_version; + } + foreach ($project['releases'] as $release) { + $status = array(); + $type = array(); + if (($k = array_search($release['version_major'], $supported)) !== FALSE) { + $status[] = dt('Supported'); + unset($supported[$k]); + } + if ((isset($recommended_version)) && ($release['version'] == $recommended_version)) { + $status[] = dt('Recommended'); + } + if ($release['version_extra'] == 'dev') { + $status[] = dt('Development'); + } + if (isset($project_info[$key])) { + if ($project_info[$key]->info['version'] == $release['version']) { + $status[] = dt('Installed'); + } + } + if (isset($release['terms']) && array_key_exists('Release type', $release['terms'])) { + foreach ($release['terms']['Release type'] as $one_type) { + if ($one_type == 'Security update') { + $status[] = dt('Security'); + } + } + } + $rows[] = array( + $key, + $release['version'], + format_date($release['date'], 'custom', 'Y-M-d'), + implode(', ', $status) + ); + } + } + + if (count($rows) == 1) { + return drush_set_error('DRUSH_PM_PROJECT_NOT_FOUND', dt('No information available.')); + } + else { + return drush_print_table($rows, TRUE); + } +} + +/** + * 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 updatecode. + */ +function drush_pm_update() { + // Signal that we will update drush core after the drush modules + // are updated, if an update to core is available. + drush_set_context('DRUSH_PM_UPDATE_ALL', TRUE); + + // Call pm-updatecode. updatedb will be called in the post-update process. + $args = func_get_args(); + array_unshift($args, 'pm-updatecode'); + call_user_func_array('drush_invoke', $args); + + // pm-updatecode will not do a core update of Drupal + // on the same invocation where non-core modules are + // updated. If there is a core update available, then + // call pm-updatecode a second time to update core + // (but only if the first run finished successfully). + if (drush_get_context('DRUSH_PM_CORE_UPDATE_AVAILABLE', FALSE) && (drush_get_error() == DRUSH_SUCCESS)) { + call_user_func_array('drush_invoke', array('pm-updatecode', 'drupal')); + } +} + +/** + * Post-command callback. + * Execute updatedb command after an updatecode - user requested `update`. + */ +function drush_pm_post_pm_update() { + // Use drush_backend_invoke to start a subprocess. Cleaner that way. + drush_backend_invoke('updatedb'); +} + +/** + * Post-command callback for updatecode. Notify about any pending DB updates. + */ +function drush_pm_post_pm_updatecode() { + // Make sure the installation API is available + require_once drush_get_context('DRUSH_DRUPAL_ROOT') . '/includes/install.inc'; + + // Load all .install files. + drupal_load_updates(); + + // @see system_requirements(). + foreach (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. Please run `drush updatedb` or visit update.php in your browser."), 'warning'); + break; + } + } + } +} + + +/** + * Deletes a directory, all files in it and all subdirectories in it (recursively). + * Use with care! + * Written by Andreas Kalsch + */ +function delete_dir($dir) { + if (substr($dir, strlen($dir)-1, 1) != '/') + $dir .= '/'; + + if ($handle = opendir($dir)) { + while ($obj = readdir($handle)) { + if ($obj != '.' && $obj != '..') { + if (is_dir($dir.$obj)) { + if (!delete_dir($dir.$obj)) { + return false; + } + } + elseif (is_file($dir.$obj)) { + if (!unlink($dir.$obj)) { + return false; + } + } + } + } + + closedir($handle); + + if (!@rmdir($dir)) { + } + return true; + } + return false; +} + +/** + * Determine a candidate destination directory for a particular site path and + * return it if it exists, optionally attempting to create the directory. + */ +function pm_dl_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 'translation': + $destination = $drupal_root . '/'; + break; + case 'profile': + $destination = $drupal_root . '/profiles/'; + break; + } + if ($create) { + drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination))); + @drush_op('mkdir', $destination, 0777, 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; +} + +/** + * Return the best destination for a particular download type we can find, + * given the drupal and site contexts. + */ +function pm_dl_destination($type) { + // Attempt 0: Use the user specified destination directory, if it exists. + $destination = drush_get_option('destination'); + if (!empty($destination)) { + $destination = rtrim($destination, '/') . '/'; + if (!is_dir($destination)) { + drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination))); + if (!drush_get_context('DRUSH_SIMULATE')) { + if (drush_confirm(dt('Would you like to create it?'))) { + @drush_op('mkdir', $destination, 0777, TRUE); + } + } + } + if (is_dir($destination)) { + return $destination; + } + else { + return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('The destination directory !destination does not appear to exist.', array('!destination' => $destination))); + } + } + + $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 ($full_site_root == substr(drush_cwd() . '/', 0, strlen($full_site_root))) { + $in_site_directory = TRUE; + } + + // 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) { + $destination = pm_dl_destination_lookup($type, $drupal_root, $full_site_root); + } + // Attempt 2: If the destination directory already exists for sites/all, then we use that. + if (empty($destination) && $drupal_root) { + $destination = pm_dl_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_dl_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_dl_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_dl_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; +} + +/** + * 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', 6) . '.x'; + // ignore-bootstrap is a temporary hack. You can't currently download a 7.x + // module after bootstrapping a 6.x site. This is needed by core-upgrade. + // Related to http://drupal.org/node/463110. + $drupal_bootstrap = drush_get_option('bootstrap_cancel') ? !drush_get_option('bootstrap_cancel') : 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_set_error('DRUSH_PM_HEAD', 'Can\'t download HEAD releases because Drupal.org project information only provides for numbered release nodes.'); + 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'; + } + } + } + } + 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; +} + +function pm_project_types() { + // Lookup the 'Project type' vocabulary to some standard strings. + $types = array( + 'core' => 'Drupal project', + 'profile' => 'Installation profiles', + 'module' => 'Modules', + 'theme' => 'Themes', + 'theme engine' => 'Theme engines', + 'translation' => 'Translations', + ); + return $types; +} + +/** + * 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(), + 'cvs' => array( + 'options' => array( + '--package-handler=cvs' => 'Use CVS to checkout and update projects.', + ' --cvsparams' => 'Add options to the `cvs` program', + ' --cvsmethod' => 'Force cvs updates or checkouts (checkout is default unless the directory is managed by a supported version control system).', + ' --cvscredentials' => 'A username and password that is sent for cvs checkout command. Defaults to anonymous:anonymous', + + ), + 'examples' => array( + 'drush [command] cck --cvscredentials=\"name:password\"' => 'Checkout should use these credentials.', + 'drush [command] cck --cvsparams=\"-C\"' => 'Overwrite all local changes (Quotes are required).', + 'drush [command] cck --cvsmethod=update' => 'Will update the project, and try to merge changes, rather than overwriting them. Any conflicts will need to be resolved manually.', + ), + ), + ); +} + +/** + * Integration with VCS in order to easily commit your changes to projects. + */ +function pm_drush_engine_version_control() { + return array( + 'svn' => array( + 'signature' => 'svn info %s', + 'options' => array( + '--version-control=svn' => 'Quickly add/remove/commit your project changes to Subversion.', + ' --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.', + ' --svnmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>', + ' --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', + ), + 'examples' => array( + 'drush [command] cck --svncommitparams=\"--username joe\"' => 'Commit changes as the user \'joe\' (Quotes are required).' + ), + ), + 'backup' => array( + 'options' => array( + '--version-control=backup' => 'Backup all project files before updates.', + ' --backup-dir' => 'Backup destination directory. Defaults to a "/backup" subdirectory inside your Drupal root.', + ), + ), + 'bzr' => array( + 'signature' => 'bzr root %s', + 'options' => array( + '--version-control=bzr' => 'Quickly add/remove/commit your project changes to Bazaar.', + ' --bzrsync' => 'Automatically add new files to the Bazaar repository and remove deleted files. Caution.', + ' --bzrcommit' => 'Automatically commit changes to Bazaar repository. You must also using the --bzrsync option.', + ' --bzrmessage' => 'Override default commit message which is: Drush automatic commit: <the drush command line used>', + ), + ), + ); +} + +/** + * Interface for version control systems. + * We use a simple object layer because we conceivably need more than one + * loaded at a time. + */ +interface drush_pm_version_control { + function pre_update(&$project); + function rollback($project); + function post_update($project); + function post_download($project); +} + +/** + * 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 = '.') { + $version_controls = explode(',', drush_get_option('version-control', 'svn,backup')); + $version_control_engines = drush_get_engines('version_control'); + + // 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($version_control_engines[$version_control])) { + if (!empty($version_control_engines[$version_control]['signature'])) { + if (drush_shell_exec($version_control_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))); + } + if (!drush_include_engine('version_control', $engine)) { + return FALSE; + } + $engine = 'drush_pm_version_control_' . $engine; + return new $engine(); +} + +/** + * Command callback. Download Drupal core or any project. + */ +function drush_pm_download() { + // Bootstrap to the highest level possible. + drush_bootstrap_max(); + + drush_include_engine('package_handler', drush_get_option('package-handler', 'wget')); + + if (!$requests = func_get_args()) { + $requests = array('drupal'); + } + + // Parse out project name and version + $requests = pm_parse_project_version($requests); + + $project_types = pm_project_types(); + $project_types_xpath = '(value="' . implode('" or value="', $project_types) . '")'; + foreach ($requests as $name => $request) { + // Don't rely on UPDATE_DEFAULT_URL since we are not fully bootstrapped. + $url = drush_get_option('source', 'http://updates.drupal.org/release-history') . '/' . $request['name'] . '/' . $request['drupal_version']; + drush_log('Downloading release history from ' . $url); + // A simple download, which is never available via CVS. + // Some hosts have allow_url_fopen disabled. + if (!$xml = @simplexml_load_file($url)) { + if (!drush_shell_exec("wget $url")) { + drush_shell_exec("curl -O $url"); + } + // Get the filename... + $filename = explode('/', $url); + $filename = array_pop($filename); + $xml = simplexml_load_file($filename); + drush_op('unlink', $filename); + } + + if ($xml) { + if ($error = $xml->xpath('/error')) { + drush_set_error('DRUSH_PM_COULD_NOT_LOAD_UPDATE_FILE', $error[0]); + } + else { + // Try to get the specified release. + if (!empty($request['version'])) { + $releases = $xml->xpath("/project/releases/release[status='published'][version='" . $request['version'] . "']"); + if (empty($releases)) { + drush_log(dt("Could not locate specified project version, downloading latest stable version"), 'notice'); + } + } + // If that did not work, get the first published release for the recommended major version. + if (empty($releases)) { + if ($recommended_major = $xml->xpath("/project/recommended_major")) { + $xpath_releases = "/project/releases/release[status='published'][version_major=" . (string)$recommended_major[0] . "]"; + $releases = @$xml->xpath($xpath_releases); + } + } + + // If there are recommended releases (no 'version_extra' elements), + // then use only recommended releases. Otherwise, use all; in + // this case, the recommended release defaults to the latest published + // release with the right recommended major version number. + $recommended_releases = array(); + if (!empty($releases)) { + foreach ($releases as $one_release) { + if (!array_key_exists('version_extra', $one_release)) { + $recommended_releases[] = $one_release; + } + } + } + + if (!empty($recommended_releases)) { + $releases = $recommended_releases; + } + + if (empty($releases)) { + drush_log(dt('There is no *recommended* release for project !project on Drupal !drupal_version. Ask the maintainer to review http://drupal.org/node/197584 and create/recommend a release in order to be compatible with drush and the drupal.org security broadcast system. A recommended development snapshot release is sufficient. Alternatively, run pm-releases command and explicity pm-download any non-recommended release that might be available.', array('!drupal_version' => $request['drupal_version'], '!project' => $request['name'])), 'ok'); + continue; + } + + // Profiles have 3 variants for a given real relase. Admin can specify using option. + // Depends on a fixed order of variations in releases list. + // Usually, the first release is chosen. + $variations = array('core', 'no-core', 'make'); + $variant = drush_get_option('variant', 'core'); + $release = (array)$releases[array_search($variant, $variations)]; + + // Determine what type of project we have, so we know where to put it. + $release['type'] = 'module'; + + if ($types = $xml->xpath('/project/terms/term[name="Projects" and ' . $project_types_xpath . ']')) { + $release['type'] = array_search($types[0]->value, $project_types); + } + + // Create basic project array. + $project = $request; + if ($project['base_project_path'] = pm_dl_destination($release['type'])) { + $project['full_project_path'] = $project['base_project_path'] . $request['name']; + $project['project_type'] = $release['type']; + if (!$version_control = drush_pm_include_version_control($project['base_project_path'])) { + return FALSE; + } + if (package_handler_install_project($project, $release)) { + // If the --destination option was not specified, then + // allow commandfiles that implement the adjust-download-destination + // hook to pick a new default location for the project. + if (drush_get_option('destination', FALSE) === FALSE) { + drush_pm_relocate_project($project, $release); + } + drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $project['full_project_path'])), 'success'); + drush_command_invoke_all('drush_pm_post_download', $project, $release); + $version_control->post_download($project); + } + } + } + } + else { + // We are not getting here since drupal.org always serves an XML response. + drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Could not download project status information from !url', array('!url' => $url))); + } + + unset($error, $release, $releases, $types); + } +} + +/** + * drush_pm_relocate_project moves projects that should be relocated to a different + * installation directory to the location they belong in. For example, + * modules that are only collections of drush commands will be installed + * to $HOME/.drush. + * + * This function is called after the project is downloaded so that its + * contents can be examined to determine its optimal installation location. + * Every drush commandfile is given a chance to examine the project contents + * and decide where the project should be located. + */ +function drush_pm_relocate_project(&$project, $release) { + // Call the get-install-location hook to see if any drush commandfiles + // would like to adjust the install location for this project. + // drush_command_invoke_all() allows the first parameter to be passed by reference. + drush_command_invoke_all_ref('drush_pm_adjust_download_destination', $project, $release); + + if (isset($project['project_install_location']) && ($project['full_project_path'] != $project['project_install_location'])) { + if (drush_move_dir($project['full_project_path'], $project['project_install_location'], TRUE)) { + $project['full_project_path'] = $project['project_install_location']; + } + else { + drush_log(dt("Project !project (!version) could not be relocated to !dest.", array('!project' => $project['name'], '!version' => $release['version'], '!dest' => $project['project_install_location'])), 'warning'); + } + } + return TRUE; +} + +/** + * Built-in adjust-download-destination 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_adjust_download_destination(&$project, $release) { + // If this project is a module, but it has no .module file, then + // check to see if it contains drush commands. If that is all + // that it contains, then install it to $HOME/.drush. + if ($release['type'] == 'module') { + $module_files = drush_scan_directory($project['full_project_path'], '/.*\.module/'); + if (empty($module_files)) { + $drush_command_files = drush_scan_directory($project['full_project_path'], '/.*\.drush.inc/'); + if (!empty($drush_command_files)) { + $install_dir = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands/'; + if (!is_dir($install_dir)) { + $install_dir = drush_server_home() . '/.drush/'; + } + // Make the .drush dir if it does not already exist + if (!is_dir($install_dir)) { + mkdir($install_dir); + } + // Change the location if the mkdir worked + if (is_dir($install_dir)) { + $project['project_install_location'] = $install_dir . basename($project['full_project_path']); + } + } + } + } +} diff --git a/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc b/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc new file mode 100644 index 00000000..2032b888 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/update_info/drupal_5.inc @@ -0,0 +1,94 @@ +<?php +// $Id: drupal_5.inc,v 1.8 2010/06/16 15:41:56 weitzman Exp $ + +function pm_update_filter(&$project) { + $update = FALSE; + switch($project['status']) { + case UPDATE_STATUS_CURRENT: + $status = dt('OK'); + $project['candidate_version'] = $project['recommended']; + break; + case UPDATE_STATUS_NOT_CURRENT: + $status = dt('Update available'); + pm_release_recommended($project); + break; + case UPDATE_STATUS_NOT_SECURE: + $status = dt('SECURITY UPDATE available'); + pm_release_recommended($project); + break; + case UPDATE_STATUS_REVOKED: + $status = dt('Installed version REVOKED'); + pm_release_recommended($project); + break; + case UPDATE_STATUS_NOT_SUPPORTED: + $status = dt('Installed version not supported'); + pm_release_recommended($project); + break; + default: + $status = dt('Ignored: !reason', array('!reason' => $project['reason'])); + $project['title'] = $project['name']; + $project['candidate_version'] = dt('Unknown'); + break; + } + return $status; +} + +function pm_update_last_check() { + return variable_get('update_status_last', 0); +} + +/** + * Command callback. Refresh update status information. + */ +function _pm_refresh() { + drush_print(dt("Refreshing update status information ...")); + update_status_refresh(); + drush_print(dt("Done.")); +} + +/** + * Get update information for all installed projects. + * + * @return An array containing remote and local versions for all installed projects + */ +function _pm_get_update_info($projects = NULL) { + // We force a refresh if the cache is not available. + if (!cache_get('update_status_info', 'cache')) { + _pm_refresh(); + } + $info = update_status_get_available(); + $data = update_status_calculate_project_data($info); + + // update_status for drupal 5 can only process modules, + // so we need to add this here for backwards compatibility + // or pm_get_project_path() will fail + foreach ($data as $project_name => $project_data) { + $data[$project_name]['project_type'] = 'module'; + } + $data = pm_get_project_path($data, 'modules'); + + return $data; +} + +/** + * Get project information from drupal.org. + * + * @param $projects An array of project names/** + * 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_STATUS_DEFAULT_URL. "/$project_name/". UPDATE_STATUS_CORE_VERSION; + $xml = drupal_http_request($url); + $data[] = $xml->data; + } + if ($data) { + $parser = new update_status_xml_parser; + $info = $parser->parse($data); + } + return $info; +} diff --git a/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc b/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc new file mode 100644 index 00000000..3da05554 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/update_info/drupal_6.inc @@ -0,0 +1,86 @@ +<?php +// $Id: drupal_6.inc,v 1.7 2010/01/27 16:29:19 weitzman Exp $ + +function pm_update_filter(&$project) { + $update = FALSE; + switch($project['status']) { + case UPDATE_CURRENT: + $status = dt('Up to date'); + $project['candidate_version'] = $project['recommended']; + break; + case UPDATE_NOT_CURRENT: + $status = dt('Update available'); + pm_release_recommended($project); + break; + case UPDATE_NOT_SECURE: + $status = dt('SECURITY UPDATE available'); + pm_release_recommended($project); + break; + case UPDATE_REVOKED: + $status = dt('Installed version REVOKED'); + pm_release_recommended($project); + break; + case UPDATE_NOT_SUPPORTED: + $status = dt('Installed version not supported'); + pm_release_recommended($project); + break; + case UPDATE_NOT_CHECKED: + $status = dt('Unable to check status'); + break; + case UPDATE_UNKNOWN: + default: + $status = dt('Unknown'); + break; + } + return $status; +} + +function pm_update_last_check() { + return variable_get('update_last_check', 0); +} + +/** + * Command callback. Refresh update status information. + */ +function _pm_refresh() { + drush_print(dt("Refreshing update status information ...")); + update_refresh(); + drush_print(dt("Done.")); +} + +/** + * Get update information for all installed projects. + * + * @return An array containing remote and local versions for all installed projects + */ +function _pm_get_update_info($projects = NULL) { + // We force a refresh if the cache is not available. + if (!cache_get('update_info', 'cache_update')) { + _pm_refresh(); + } + $info = update_get_available(); + $data = update_calculate_project_data($info); + $data = pm_get_project_path($data, 'includes'); + 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; +} diff --git a/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc b/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc new file mode 100644 index 00000000..691f8888 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/update_info/drupal_7.inc @@ -0,0 +1,91 @@ +<?php +// $Id: drupal_7.inc,v 1.7 2010/04/13 05:13:49 weitzman Exp $ + +function pm_update_filter(&$project) { + $update = FALSE; + switch($project['status']) { + case UPDATE_CURRENT: + $status = dt('Up to date'); + $project['candidate_version'] = $project['recommended']; + break; + case UPDATE_NOT_CURRENT: + $status = dt('Update available'); + pm_release_recommended($project); + break; + case UPDATE_NOT_SECURE: + $status = dt('SECURITY UPDATE available'); + pm_release_recommended($project); + break; + case UPDATE_REVOKED: + $status = dt('Installed version REVOKED'); + pm_release_recommended($project); + break; + case UPDATE_NOT_SUPPORTED: + $status = dt('Installed version not supported'); + pm_release_recommended($project); + break; + case UPDATE_NOT_CHECKED: + $status = dt('Unable to check status'); + break; + case UPDATE_UNKNOWN: + default: + $status = dt('Unknown'); + break; + } + return $status; +} + +function pm_update_last_check() { + return variable_get('update_last_check', 0); +} + +/** + * Command callback. Refresh update status information. + */ +function _pm_refresh() { + drush_print(dt("Refreshing update status information ...")); + update_get_available(TRUE); + drush_print(dt("Done.")); +} + +/** + * Get update information for all installed projects. + * + * @return An array containing remote and local versions for all installed projects + */ +function _pm_get_update_info($projects = NULL) { + // We force a refresh if the cache is not available. + if (!cache_get('update_info', 'cache_update')) { + _pm_refresh(); + } + $info = update_get_available(); + $data = update_calculate_project_data($info); + $data = pm_get_project_path($data, 'includes'); + 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; +} diff --git a/sites/all/modules/drush/commands/pm/updatecode.pm.inc b/sites/all/modules/drush/commands/pm/updatecode.pm.inc new file mode 100644 index 00000000..22ac9779 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/updatecode.pm.inc @@ -0,0 +1,438 @@ +<?php +// $Id: updatecode.pm.inc,v 1.10 2010/04/09 20:08:46 weitzman Exp $ + +/** + * Command callback. Displays update status info and allows to update installed projects. + * Pass specific projects as arguments, otherwise we update all that have candidate releases. + * + * This command prompts for confirmation before updating, so it is safe to run just to check on + * In this case, say at the confirmation prompt. + */ +function drush_pm_updatecode() { + // 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'); + + // Get update status information. + $projects = _pm_get_update_info(); + + // Get specific requests + $requests = func_get_args(); + + // Parse out project name and version + $requests = pm_parse_project_version($requests); + + // Preprocess releases + if (!empty($requests)) { + // Force update projects where a specific version is reqested + foreach ($requests as $name => $request) { + if (!isset($projects[$name])) { + // Catch projects with no version data (common for CVS checkouts + // if you don't have CVS deploy installed). + $projects[$name] = array( + 'title' => $name, + 'existing_version' => 'Unknown', + 'status'=> DRUSH_PM_NO_VERSION, + ); + } + else if (!empty($request['version'])) { + // Match the requested release + $release = pm_get_release($request, $projects[$name]); + if (!$release) { + $projects[$name]['status'] = DRUSH_PM_REQUESTED_NOT_FOUND; + } + else if ($release['version'] == $projects[$name]['existing_version']) { + $projects[$name]['status'] = DRUSH_PM_REQUESTED_CURRENT; + } + else { + $projects[$name]['status'] = DRUSH_PM_REQUESTED_UPDATE; + } + // Set the candidate version to the requested release + $projects[$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($projects, $rows); + + // Pipe preparation + if (drush_get_context('DRUSH_PIPE')) { + $pipe = ""; + foreach($projects 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 = FALSE; + } + + $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:")); + drush_print_table($rows, TRUE); + drush_print(); + + // If specific project updates were requested then remove releases for all others + if (!empty($requests)) { + foreach ($updateable as $name => $project) { + if (!isset($requests[$name])) { + unset($updateable[$name]); + } + } + } + + if (isset($updateable['drupal'])) { + $drupal_project = $updateable['drupal']; + unset($projects['drupal']); + unset($updateable['drupal']); + $module_list = array_keys($projects); + + // We can only upgrade drupal core if there are no non-core + // modules enabled. _pm_update_core will disable the + // modules passed in, and insure that they are enabled again + // when we're done. However, each run of pm-updatecode will + // update -either- core, or the non-core modules; never both. + // This simplifies rollbacks. + if (empty($updateable)) { + return _pm_update_core($drupal_project, $module_list); + } + // If there are modules other than drupal core enabled, then go + // ahead and print instructions on how to upgrade core. We will + // also continue, allowing the user to upgrade any upgradable + // modules if desired. + else { + drush_print(dt("NOTE: A code update for the Drupal core is available.")); + if (drush_get_context('DRUSH_PM_UPDATE_ALL', FALSE)) { + drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n")); + } + else { + drush_print(dt("Drupal core cannot be updated at the same time that non-core modules are updated. In order to update Drupal core with this tool, first allow the update of the modules listed above to complet, and then run pm-updatecode again.\n")); + } + drush_set_context('DRUSH_PM_CORE_UPDATE_AVAILABLE', TRUE); + } + } + + if (empty($updateable)) { + return drush_log(dt('No code updates available.'), 'ok'); + } + + // Offer to update to the identified releases + return pm_update_packages($updateable); +} + +/** + * Update drupal core, following interactive confirmation from the user. + * + * @param $project + * The drupal project information from the drupal.org update service, + * copied from $projects['drupal']. @see drush_pm_updatecode. + * @param $module_list + * A list of the non-core modules that are enabled. These must be disabled + * before core can be updated. + */ +function _pm_update_core(&$project, $module_list = array()) { + drush_include_engine('package_handler', drush_get_option('package-handler', 'wget')); + $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.")); + if(!drush_confirm(dt('Do you really want to continue?'))) { + drush_die('Aborting.'); + } + + // 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'])) { + mkdir($project['full_project_path']); + } + + // Create a list of files and folders that are user-customized or otherwise + // not part of the update process + $project['skip_list'] = array('backup', 'sites', $project['path']); + + // 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; + } + + // Make a list of every item at the root of core except 'sites' + $items_to_test = drush_scan_directory($project['full_project_path'], '/.*/', array('.', '..', 'sites', '.svn'), 0, FALSE, 'basename', 0, TRUE); + + $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; + } + + // 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']); + + // 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_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 = rename($filename, $dest_dir . '/' . basename($filename)); + } + } + return TRUE; +} + +/** + * Update packages according to an array of releases, following interactive + * confirmation from the user. + * + * @param $projects + * 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($projects) { + drush_include_engine('package_handler', drush_get_option('package-handler', 'wget')); + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + drush_print(dt('Code updates will be made to the following projects:')); + foreach($projects as $project) { + $print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], "; + } + drush_print(substr($print, 0, strlen($print)-2)); + drush_print(); + drush_print(dt("Note: Updated projects can potentially break your site. It is NOT recommended to update production sites without prior testing.")); + drush_print(dt("Note: A backup of your package 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?'))) { + drush_die('Aborting.'); + } + + // Now we start the actual updating. + foreach($projects 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']))); + // Create the projects directory and base (parent) directory. + $project['full_project_path'] = $drupal_root . '/' . $project['path']; + // Check that the directory exists, and is where we expect it to be. + 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; + } + // Run update on one project + if (pm_update_project($project, $version_control) === FALSE) { + return FALSE; + } + pm_update_complete($project, $version_control); + } + // Clear the cache, since some projects could have moved around. + drush_drupal_cache_clear_all(); +} + +/** + * 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) { + // 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']))); + } + + 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'); + if (isset($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']); + } +} + +function pm_project_filter(&$projects, &$rows) { + $updateable = array(); + foreach ($projects as $key => $project) { + if (empty($project['title'])) { + continue; + } + 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_NO_VERSION: + $status = dt('No version information found (if you have a CVS checkout you should install CVS Deploy module)'); + break; + case DRUSH_PM_REQUESTED_NOT_FOUND: + $status = dt('Specified version not found'); + break; + default: + $status = pm_update_filter($project); + break; + } + + // Persist candidate_version in $projects (plural). + if (empty($project['candidate_version'])) { + $projects[$key]['candidate_version'] = $project['existing_version']; // Default to no change + } + else { + $projects[$key]['candidate_version'] = $project['candidate_version']; + } + if (!empty($project['updateable'])) { + $updateable[$key] = $project; + } + $rows[] = array($project['title'], $project['existing_version'], $projects[$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; + } +} + +/** + * 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_project_info() + */ +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; + } +} diff --git a/sites/all/modules/drush/commands/pm/version_control/backup.inc b/sites/all/modules/drush/commands/pm/version_control/backup.inc new file mode 100644 index 00000000..965ed1b5 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/version_control/backup.inc @@ -0,0 +1,60 @@ +<?php +// $Id: backup.inc,v 1.4 2010/04/02 04:06:38 weitzman Exp $ + +/** + * @file Drush pm directory copy backup extension + */ + +class drush_pm_version_control_backup implements drush_pm_version_control { + + /** + * Implementation of pre_update(). + */ + public function pre_update(&$project, $items_to_test = array()) { + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + + // Save the date to be used in the backup directory's path name. + $date = date('YmdHis', $_SERVER['REQUEST_TIME']); + + $backup_dir = drush_get_option('backup-dir', $drupal_root . '/backup'); + $backup_dir = rtrim($backup_dir, '/'); + @drush_op('mkdir', $backup_dir, 0777); + $backup_dir .= '/modules'; + @drush_op('mkdir', $backup_dir, 0777); + $backup_dir .= "/$date"; + @drush_op('mkdir', $backup_dir, 0777); + $backup_target = $backup_dir . '/'. $project['name']; + // Save for rollback or notifications. + $project['backup_target'] = $backup_target; + if (!drush_op('rename', $project['full_project_path'], $backup_target)) { + 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))); + } + return TRUE; + } + + /** + * Implementation of rollback(). + */ + public function rollback($project) { + if (drush_op('rename', $project['backup_target'], $project['full_project_path']) && is_dir($project['full_project_path'])) { + 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 ($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 + } +} diff --git a/sites/all/modules/drush/commands/pm/version_control/bzr.inc b/sites/all/modules/drush/commands/pm/version_control/bzr.inc new file mode 100644 index 00000000..3fa1417a --- /dev/null +++ b/sites/all/modules/drush/commands/pm/version_control/bzr.inc @@ -0,0 +1,126 @@ +<?php +// $Id: bzr.inc,v 1.6 2010/04/02 04:06:38 weitzman Exp $ + +/** + * @file Drush pm BZR extension + */ + +class drush_pm_version_control_bzr implements drush_pm_version_control { + + /** + * Helper function: get the root of a repository path. + * + * @param $path + * The path to check. + * @return + * An absolute path to the repository root. + */ + public function bzr_get_repository_root($path) { + if (drush_shell_exec('bzr root %s', $path)) { + $output = drush_shell_exec_output(); + return reset($output); + } + } + + /** + * Implementation of pre_update(). + */ + public function pre_update(&$project, $items_to_test = array()) { + // TODO: Add similar checks to SVN? + } + + /** + * Implementation of post_update(). + */ + public function post_update($project) { + if (is_versioned($project['full_project_path']) && sync($project)) { + // Only attempt commit on a sucessful sync + commit($project); + } + } + + /** + * Implementation of post_download(). + */ + public function post_download($project) { + if (is_versioned($project['full_project_path']) && sync($project)) { + // Only attempt commit on a sucessful sync + commit($project); + } + } + + /** + * Automatically add any unversioned files to Bzr and remove any files + * that have been deleted on the file system + */ + private function sync($project) { + if (drush_get_option('bzrsync')) { + $errors = ''; + + // All paths returned by bzr status are relative to the repository root. + $root = bzr_get_repository_root($project['full_project_path']); + + if (drush_shell_exec('bzr status -S %s', $project['full_project_path'])) { + $output = drush_shell_exec_output(); + + // Parse the output of bzr status to get the list of added and removed + // files. + $new_files = array(); + $removed_files = array(); + foreach ($output as $line) { + if (preg_match('/^\? *(.*)/', $line, $matches)) { + $new_files[] = $root . '/' . $matches[1]; + } + if (preg_match('/^\ D *(.*)/', $line, $matches)) { + $removed_files[] = $root . '/' . $matches[1]; + } + } + + // Proceed adding new files. + foreach (array_chunk($new_files, 100) as $chunk) { + $chunk = array_map('escapeshellarg', $chunk); + if (!drush_shell_exec('bzr add ' . implode(' ', $chunk))) { + $errors .= implode("\n", drush_shell_exec_output()); + } + } + + // Proceed removing old files. + foreach (array_chunk($removed_files, 100) as $chunk) { + $chunk = array_map('escapeshellarg', $chunk); + if (!drush_shell_exec('bzr remove ' . implode(' ', $chunk))) { + $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: \n") . implode(' ', $_SERVER['argv']); + } + if (drush_shell_exec('bzr commit -m %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.")); + } + } +} diff --git a/sites/all/modules/drush/commands/pm/version_control/svn.inc b/sites/all/modules/drush/commands/pm/version_control/svn.inc new file mode 100644 index 00000000..a4ba6ea0 --- /dev/null +++ b/sites/all/modules/drush/commands/pm/version_control/svn.inc @@ -0,0 +1,129 @@ +<?php +// $Id: svn.inc,v 1.13 2010/06/03 13:03:01 greg1anderson Exp $ + +/** + * @file Drush pm SVN extension + */ + +class drush_pm_version_control_svn implements drush_pm_version_control { + + /** + * Implementation of pre_update(). + */ + public function pre_update(&$project, $items_to_test = array()) { + // If items to test is empty, test everything; otherwise, pass just + // the list of files to test to svn status. + $status_files = implode(' ', array_keys($items_to_test)); + + // Check the project directory looks clean + if (drush_shell_exec('cd ' . $project['full_project_path'] . ' && svn status '. drush_get_option('svnstatusparams') .' '. $status_files)) { + $output = preg_grep('/^[ ACDMRX?!~][ CM][ L][ +][ SX][ K]/', 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 uncommmitted 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())))); + } + + // Check for incoming updates + if (drush_shell_exec('cd ' . $project['full_project_path'] . ' && svn status -u '. drush_get_option('svnstatusparams') .' '. $status_files)) { + $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 Subversion \ninstalled, that this directory is a subversion working copy and 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 uncommmitted 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.")); + } + } +} diff --git a/sites/all/modules/drush/commands/sql/sql.drush.inc b/sites/all/modules/drush/commands/sql/sql.drush.inc new file mode 100644 index 00000000..4273d1e9 --- /dev/null +++ b/sites/all/modules/drush/commands/sql/sql.drush.inc @@ -0,0 +1,559 @@ +<?php +// $Id: sql.drush.inc,v 1.49 2010/06/08 20:24:42 greg1anderson Exp $ + +/** + * @file Drush sql commands + */ + +/** + * Implementation of hook_drush_help(). + */ +function sql_drush_help($section) { + switch ($section) { + case 'drush:sql-conf': + return dt('Show database connection details.'); + case 'drush:sql-connect': + return dt('A string which connects to the current database.'); + case 'drush:sql-cli': + return dt('Quickly enter the mysql command line.'); + case 'drush:sql-dump': + return dt('Prints the whole database to STDOUT or save to a file.'); + case 'drush:sql-query': + return dt("Usage: drush [options] sql-query <query>...\n<query> is a SQL statement, which can alternatively be passed via STDIN. Any additional arguments are passed to the mysql command directly."); + case 'drush:sql-sync': + return dt("Usage: drush [options] sql-sync <source_alias> <target_alias>.\n <source_alias> and <target_alias> are site-aliases, or names of directories under \'sites\'. These determine from where and to where you want your database copied."); + } +} + +/** + * Implementation of hook_drush_command(). + */ +function sql_drush_command() { + $options['--database'] = 'The DB connection key if using multiple connections in settings.php.'; + if (drush_drupal_major_version() >= 7) { + $options['--target'] = 'The name of a target within the specified database.'; + } + + $items['sql-conf'] = array( + 'description' => 'Print database connection details using print_r().', + 'arguments' => array( + 'all' => 'Show all database connections, instead of just one.', + ), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + 'options' => $options, + ); + $items['sql-connect'] = array( + 'description' => 'A string for connecting to the DB.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + 'options' => $options, + 'examples' => array( + '`drush sql connect` < example.sql' => 'Import sql statements from a file into the current database.', + ), + ); + $items['sql-dump'] = array( + 'callback' => 'drush_sql_dump_execute', + 'description' => 'Exports the Drupal DB as SQL using mysqldump.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + '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.com', + ), + 'options' => array( + '--result-file' => 'Save to a file. The file should be relative to Drupal root. Recommended.', + '--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.', + '--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' => 'Wipe existing tables.', + '--data-only' => 'Omit CREATE TABLE statements. Postgres only.', + '--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.', + ) + $options, + ); + $items['sql-query'] = array( + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE, + 'description' => 'Execute a query against the site database.', + 'examples' => array( + 'drush sql-query "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored.', + ), + 'arguments' => array( + 'query' => 'A SQL query. Mandatory.', + ), + 'options' => array( + '--extra' => 'Add custom options to the mysql command.', + ) + $options, + 'aliases' => array('sqlq'), + ); + $items['sql-sync'] = array( + 'description' => 'Copy source database to target database using rsync.', + 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, + 'examples' => array( + 'drush sql-sync @dev @prod' => 'Copy the DB defined in sites/dev to the DB in sites/prod.', + ), + '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.', + '--structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. 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.', + '--target-db-url' => '', + '--target-remote-port' => '', + '--target-remote-host' => '', + '--target-dump' => '', + '--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' => 'Account to use when creating a new database. Optional.', + '--db-su-pw' => 'Password for the "db-su" account. Optional.', + ), + ); + if (drush_drupal_major_version() >= 7) { + + $items['sql-sync']['options'] += array( + '--source-target' => 'The name of a target within the SOURCE database.', + '--destination-target' => 'The name of a target within the specified DESTINATION database.', + ); + } + $items['sql-cli'] = array( + 'description' => "Open a SQL command-line interface using Drupal's credentials.", + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION, + 'options' => $options, + 'aliases' => array('sqlc'), + ); + return $items; +} + +/** + * Command callback. Displays the Drupal site's database connection string. + */ +function drush_sql_conf() { + if (drush_get_option('db-url', FALSE)) { + $db_spec['db-url'] = $GLOBALS['db_url']; + } + elseif (drush_get_option('all', FALSE)) { + $db_spec = $GLOBALS['databases']; + if (!isset($db_spec)) { + $db_spec = array('default' => array('default' => _drush_sql_get_db_spec())); + } + } + if (!isset($db_spec)) { + $db_spec = _drush_sql_get_db_spec(); + } + drush_backend_set_result($db_spec); + if (!drush_get_option('show-passwords', FALSE)) { + drush_unset_recursive($db_spec, 'password'); + } + drush_print_r($db_spec); +} + +/** + * 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 ' . (drush_get_context('DRUSH_DEBUG') ? ' -v' : ''); + $command .= _drush_sql_get_credentials($db_spec); + break; + case 'pgsql': + $command = 'psql'; + $command .= _drush_sql_get_credentials($db_spec); + break; + } + return $command; +} + +function drush_sql_connect() { + drush_print(_drush_sql_connect()); +} + +/** + * Command callback. Outputs the entire Drupal database in SQL format using mysqldump. + */ +function drush_sql_dump_execute() { + $exec = drush_sql_dump(); + // Avoid the php memory of the $output array in drush_shell_exec(). + $return = drush_op('system', $exec); + return $return; +} + +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 statement. + * + * @param db_spec + * For D5/D6, a $db_url. For D7, a target in the default DB connection. + * @return string + * A mysqldump statement that is ready for executing. + */ +function drush_sql_dump($db_spec = NULL) { + return drush_sql_build_dump_command(drush_sql_get_table_selection(), $db_spec); +} + +function drush_sql_build_dump_command($tabel_selection, $db_spec = NULL) { + $skip_tables = $tabel_selection['skip']; + $structure_tables = $tabel_selection['structure']; + $tables = $tabel_selection['tables']; + + $ignores = array(); + $skip_tables += $structure_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']; + + switch (_drush_sql_get_scheme($db_spec)) { + case 'mysql': + $exec = 'mysqldump' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); + if ($file = drush_get_option('result-file')) { + $exec .= ' --result-file '. $file; + } + $extra = ' --single-transaction --opt -Q' . _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 ' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); + if ($file = drush_get_option('result-file')) { + $exec .= ' --file '. $file; + } + // Unlike psql, pg_dump does not take a '-d' flag before the database name. + // We'll put the database name in 'by hand' and then clear the name from + // the record to prevent _drush_sql_get_credentials from inserting a -d term + // that pg_dump does not understand. + $extra = ' ' . $db_spec['database']; + $db_spec['database'] = null; + $extra .= _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; + } + + return $exec; +} + +/** + * 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 funciton 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('' => 'options', '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) { + return _drush_sql_query($query); +} + +function _drush_sql_query($query, $db_spec = NULL) { + $scheme = _drush_sql_get_scheme($db_spec); + + // Inject table prefixes as needed. + if (drush_drupal_major_version() >= 7) { + $query = Database::getConnection()->prefixTables($query); + } + else { + $query = db_prefix_tables($query); + } + + // Convert mysql 'show tables;' query into something pgsql understands + if (($scheme == 'pgsql') && ($query == 'show tables;')) { + $query = "select tablename from pg_tables where schemaname='public';"; + } + + // Save $query to a tmp file. We will redirect it in. + if ($file = drush_save_data_to_temp_file($query)) { + switch ($scheme) { + case 'mysql': + $exec = 'mysql' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); + $exec .= _drush_sql_get_credentials($db_spec); + $exec .= ' ' . drush_get_option('extra'); + $exec .= " < $file"; + + break; + case 'pgsql': + $exec = 'psql'; + $exec .= _drush_sql_get_credentials($db_spec); + $exec .= (drush_get_context('DRUSH_VERBOSE') ? '' : ' -q'); + $exec .= ' ' . (drush_get_option('extra') ? drush_get_option('extra') : "--no-align --field-separator=$'\t' --pset footer=off"); + $exec .= " --file $file"; + break; + } + + $return = drush_op('system', $exec) !== FALSE; + return $return; + } +} + +function drush_sql_cli() { + switch (_drush_sql_get_scheme()) { + case 'mysql': + $command = 'mysql ' . (drush_get_context('DRUSH_VERBOSE') ? ' -v' : ''); + $command .= _drush_sql_get_credentials(); + break; + case 'pgsql': + $command = ' psql'; + $command .= _drush_sql_get_credentials(); + break; + } + proc_close(proc_open($command, array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes)); +} + + +////////////////////////////////////////////////////////////////////////////// +// SQL SERVICE HELPERS + +/** + * Get a database specification for the active DB connection. Honors the + * 'database' and 'target command' line options. + * + * @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'); + + switch (drush_drupal_major_version()) { + case 5: + case 6: + $url = $GLOBALS['db_url']; + // TODO: array version not working? + $url = is_array($url) ? $url[$database] : $url; + return drush_convert_db_from_db_url($url); + + 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_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', $db_spec['port']); + } + } + + return $db_spec; +} + +/** + * Determine where to store an sql dump file. This + * function is called by sql-sync if the caller did + * not explicitly specify a dump file to use. + * + * @param db_spec + * Information about the database being dumped; used + * to generate the filename. + * @return string + * The path to the dump file + */ +function drush_sql_dump_file(&$db_spec) { + // 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_get_option(array('source-dump-dir', 'dump-dir')); + if (isset($dump_dir)) { + $dump_file = $dump_dir . '/' . $filename_pattern . '.sql'; + } + // If the --dump-dir option is not set, then store + // the sql dump in a temporary file. + else { + $dump_file = drush_tempnam($filename_pattern . '.sql.'); + $db_spec['dump-is-temp'] = TRUE; + } + + 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(); + } + + switch (_drush_sql_get_scheme($db_spec)) { + case 'mysql': + $cred = ' -h' . $db_spec['host'] . + (empty($db_spec['port']) ? '' : ' -P' . $db_spec['port']) . + ' -u' . $db_spec['username'] . + (empty($db_spec['password']) ? '' : ' -p' . $db_spec['password']) . ' ' . $db_spec['database']; + break; + case 'pgsql': + $cred = (isset($db_spec['database']) ? ' -d ' . (empty($db_spec['database']) ? 'template1' : $db_spec['database']) : '') . + (empty($db_spec['host']) ? ' -h localhost ' : ' -h ' . $db_spec['host']) . + (empty($db_spec['port']) ? ' -p 5432 ' : ' -p ' . $db_spec['port']); + // Adding '-U' will cause Postgres to prompt for a password, so disable this for now. + // Use "sudo -u postgres drush sql ..." to access the database without a password, + // presuming that "postgres" is the database superuser and you have + // "local all all ident sameuser" in your Postgres pg_hba.conf file. + // See: http://drupal.org/node/438828 + $cred .= ' -U ' . $db_spec['username'] . ' '; + break; + } + return escapeshellcmd($cred); +} + +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 5: + case 6: + return dt('Unable to parse DB connection string'); + default: + return dt('Unable to parse DB connection array'); + } +} diff --git a/sites/all/modules/drush/commands/sql/sync.sql.inc b/sites/all/modules/drush/commands/sql/sync.sql.inc new file mode 100644 index 00000000..e796f652 --- /dev/null +++ b/sites/all/modules/drush/commands/sql/sync.sql.inc @@ -0,0 +1,344 @@ +<?php +// $Id: sync.sql.inc,v 1.26 2010/04/22 10:14:42 weitzman Exp $ + +require_once DRUSH_BASE_PATH . '/commands/core/rsync.core.inc'; + +function drush_sql_sync($source = null, $destination = null) { + $source_database = drush_get_option('source-database', 'default'); + $source_target = drush_get_option('source-target'); + $target_database = drush_get_option('target-database', 'default'); + $target_target = drush_get_option('target-target'); + + // + // If the destination was not explicitly set, but a particular + // target database was specified on the command line, then we + // will implicitly assume that the destination alias is the + // same as the source alias. + // + if (!isset($destination) && (isset($target_database) || (isset($target_target)))) { + $destination = $source; + } + + // + // If there is no destination specification, then exit. + // + if (!isset($destination)) { + drush_print(dt("You must specify a destination target.")); + exit(1); + } + // + // Default branch: copy 'sync' with the specified source + // and destination. + // + else { + _drush_sql_sync($source, $destination, TRUE); + } +} + +function _drush_sql_sync($source, $destination, $show_warning = TRUE) { + // Preflight destination in case it defines the alias used by the source + _drush_sitealias_get_record($destination); + // After preflight, get source and destination settings + $source_settings = drush_sitealias_get_record($source); + $destination_settings = drush_sitealias_get_record($destination); + + // Insure that we have database records for the source and destination + // alias records. sitealias_get_databases_from_record will cache the + // database info inside the alias records, and drush_sitealias_set_alias_context + // will copy the database record into the 'alias' context. We do not + // actually use the databases record at this time. + sitealias_get_databases_from_record($source_settings); + sitealias_get_databases_from_record($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) { + // Evaluate the source and destination specifications into options. + // The options from the 'source-*' and 'target-*' aliases are set + // in a drush context that has a lower priority than the command-line + // options; this allows command-line options to override the default + // values specified in a site-alias. + drush_sitealias_set_alias_context($source_settings, 'source-'); + drush_sitealias_set_alias_context($destination_settings, 'target-'); + + // Get the options for the source and target databases + $source_db_url = _drush_sql_get_spec_from_options('source-', FALSE); + // The host may have special ssh requirements + $source_remote_ssh_options = drush_get_option('source-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_sql_get_spec_from_options('target-', FALSE); + // The host may have special ssh requirements + $target_remote_ssh_options = drush_get_option('target-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)) { + 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)) { + 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_get_option('source-dump'); + $target_dump = drush_get_option('target-dump'); + $use_temp_files = drush_get_option('temp'); + $source_is_tmp = FALSE; + $target_is_tmp = FALSE; + if (!isset($source_db_url['remote-host']) && !isset($target_db_url['remote-host'])) { + if (isset($source_dump)) { + $target_dump = $source_dump; + } + else { + if (!isset($target_dump)) { + $target_dump = drush_sql_dump_file($target_db_url); + $target_is_tmp = TRUE; + } + $source_dump = $target_dump; + } + } + if (!isset($target_dump)) { + $target_dump = drush_sql_dump_file($target_db_url); + $target_is_tmp = TRUE; + } + if (!isset($source_dump)) { + $source_dump = drush_sql_dump_file($source_db_url); + $source_is_tmp = TRUE; + $source_rsync_options['remove-source-files'] = TRUE; + } + + if (isset($source_db_url['remote-host']) && isset($target_db_url['remote-host'])) { + $local_file = drush_tempnam($source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.'); + } + elseif (!isset($source_db_url['remote-host'])) { + $local_file = $source_dump; + } + elseif (!isset($target_db_url['remote-host'])) { + $local_file = $target_dump; + } + + // 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_get_option(array('no-sync', 'source-no-sync')); + $no_dump = drush_get_option(array('no-dump', 'source-no-dump')); + $no_cache = drush_get_option(array('no-cache', 'source-no-cache')); + if (!isset($no_cache)) { + $cache = drush_get_option(array('cache', 'source-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 (isset($cache)) { + if (file_exists($local_file) && (filesize($local_file) > 0)) { + if ((filemtime($local_file) - time()) < ($cache * 60 * 60)) { + drush_log(dt('Modification time of local dump file is less than !cache hours old. Use the --no-cache option to force a refresh.', array('!cache' => $cache))); + $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') && $show_warning) { + + // 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(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))); + } + drush_print(); + } + else { + // Check to see if we are using a temporary file in a situation + // where the user did not specify "--temp". + if (($source_is_tmp || $target_is_tmp) && (!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\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.')); + } + + $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 from !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination))); + drush_print(); + } + + if (array_key_exists('tables', $table_selection) && (count($table_selection['tables']) > 0)) { + drush_print(dt(' Only the following tables will be transferred: !list', array('!list' => implode(',', $table_selection['tables'])))); + drush_print(); + } + elseif (!empty($table_selection)) { + $skip_tables_list = implode(',', $table_selection['skip'] + $table_selection['structure']); + if(!empty($skip_tables_list)) { + drush_print(dt(' The following tables will be skipped: !list', array('!list' => $skip_tables_list))); + drush_print(); + } + } + + // TODO: actually make the backup if desired. + drush_print(dt("You might want to make a backup first, using sql_dump command.\n")); + if (!drush_confirm(dt('Do you really want to continue?'))) { + drush_die('Aborting.'); + } + } + + if (isset($source_db_url['remote-host'])) { + $source_remote_user = drush_get_option('source-remote-user'); + if (isset($source_remote_user)) { + $source_at ='@'; + $source_remote_pass = drush_get_option('source-remote-pass') ? ':' . drush_get_option('source-remote-pass') : ''; + } + + if (!isset($no_dump)) { + $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 (!isset($source_is_tmp)) { + $source_intermediate = $source_dump . '-' . date("U"); + $mv_intermediate = '; mv -f ' . $source_intermediate . ' ' . $source_dump; + } + drush_set_option('result-file', $source_intermediate); + $dump_exec = drush_sql_build_dump_command($table_selection, $source_db_url) . $mv_intermediate; + if (isset($cache) && !isset($source_is_tmp)) { + // 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'; + } + $dump_exec = "ssh $source_remote_ssh_options $source_remote_user$source_at" . $source_db_url['remote-host'] . " " . escapeshellarg($dump_exec); + } + } + else { + if (!isset($no_dump)) { + drush_set_option('result-file', $local_file); + $dump_exec = drush_sql_build_dump_command($table_selection, $source_db_url); + } + $no_sync = TRUE; + } + + // Call sql-dump, either on the local machine or remotely via ssh, as appropriate. + if (!empty($dump_exec)) { + drush_op('system', $dump_exec); + // TODO: IF FAILURE THEN ABORT + } + + // 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). + drush_core_call_rsync($source_remote_user . $source_at . $source_db_url['remote-host'] . ':' . $source_dump, $local_file, $source_rsync_options); + } + + // 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_get_option('create-db'); + if (isset($create_db)) { + $create_db_target = $target_db_url; + $create_db_target['database'] = ''; + $db_superuser = drush_get_option(array('db-su', 'target-db-su')); + if (isset($db_superuser)) { + $create_db_target['username'] = $db_superuser; + } + $db_su_pw = drush_get_option(array('db-su-pw', 'target-db-su-pw')); + if (isset($db_su_pw)) { + $create_db_target['password'] = $db_su_pw; + } + $db_su_connect = _drush_sql_connect($create_db_target); + switch (_drush_sql_get_scheme($target_db_url)) { + case 'mysql': + $pre_import_commands = 'echo "DROP DATABASE IF EXISTS ' . $target_db_url['database'] . '; CREATE DATABASE ' . $target_db_url['database'] . '; GRANT ALL PRIVILEGES ON ' . $target_db_url['database'] . '.* TO \'' . $target_db_url['username'] . '\'@\'' . $target_db_url['host'] . '\' IDENTIFIED BY \'' . $target_db_url['password'] . '\';" | ' . $db_su_connect . '; '; + break; + case 'pgsql': + $pre_import_commands = 'echo "drop database if exists ' . $target_db_url['database'] . '; create database ' . $target_db_url['database'] . ';" | ' . $db_su_connect . '; '; + break; + } + } + + // Generate the import command + $import_command = _drush_sql_connect($target_db_url); + switch (_drush_sql_get_scheme($target_db_url)) { + case 'mysql': + $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' ' : '--silent'); + break; + case 'pgsql': + $import_command .= ' ' . (drush_get_context('DRUSH_DEBUG') ? ' ' : '-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 = drush_get_option('target-remote-user'); + if (isset($target_remote_user)) { + $target_at ='@'; + $target_remote_pass = drush_get_option('target-remote-pass') ? ':' . drush_get_option('target-remote-pass') : ''; + } + + drush_core_call_rsync($local_file, $target_remote_user . $target_at . $target_db_url['remote-host'] . ':' . $target_dump, $target_rsync_options); + + $connect_exec = $pre_import_commands . $import_command . ' < ' . $target_dump; + $import_exec = "ssh $target_remote_ssh_options $target_remote_user$target_at" . $target_db_url['remote-host'] . ' ' . escapeshellarg($connect_exec); + // delete the remote target file if it is a temporary file + if ($target_is_tmp) { + $import_exec .= '; rm -f ' . escapeshellarg($target_dump); + } + } + else { + $import_exec = $pre_import_commands . $import_command . ' < ' . $local_file; + } + + drush_op('system', $import_exec); + } + } +} diff --git a/sites/all/modules/drush/drush b/sites/all/modules/drush/drush new file mode 100755 index 00000000..ce770099 --- /dev/null +++ b/sites/all/modules/drush/drush @@ -0,0 +1,53 @@ +#!/usr/bin/env sh +# $Id: drush,v 1.20 2010/06/17 04:39:41 greg1anderson Exp $ +# +# This script is a simple wrapper that will run Drush with the most appropriate +# php executable it can find. +# + +# Get the absolute path of this executable +ORIGDIR=$(pwd) +SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0") + +# 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) + SELF_PATH=$(cd $DIR && cd $(dirname -- "$SYM") && pwd)/$(basename -- "$SYM") +done +cd "$ORIGDIR" + +# Build the path to drush.php. +SCRIPT_PATH=$(dirname $SELF_PATH)/drush.php +case $(uname -a) in + CYGWIN*) + SCRIPT_PATH=$(cygpath -w -a -- "$SCRIPT_PATH") ;; +esac + +# If not exported and term is set determine and export the number of columns. +if [ -z $COLUMNS ] && [ -n "$TERM" ]; then + # Note to cygwin users: if you are getting "tput: command not found", + # install the ncurses package to get it. + export COLUMNS=$(tput cols) +fi + +# Special case for *AMP installers, since they normally don't set themselves as the default cli php out of the box. +for php in /Applications/MAMP/bin/php5/bin/php /Applications/MAMP/bin/php5.2/bin/php /Applications/MAMP/bin/php5.3/bin/php /opt/lampp/bin/php /Applications/xampp/xamppfiles/bin/php /Applications/acquia-drupal/php/bin/php; do + if [ -x $php ]; then + exec $php $SCRIPT_PATH --php="$php" "$@" + fi +done + +# We check for a command line (cli) version of php, and if found use that. +which php-cli >/dev/null 2>&1 +if [ "$?" = 0 ] ; then + exec php-cli $SCRIPT_PATH --php="php-cli" "$@" +else + # Alternatively we run with straight php, which works on most other systems. + # The --php=`which php` is for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 + exec php $SCRIPT_PATH --php=`which php` "$@" +fi diff --git a/sites/all/modules/drush/drush.api.php b/sites/all/modules/drush/drush.api.php new file mode 100644 index 00000000..87dae7f4 --- /dev/null +++ b/sites/all/modules/drush/drush.api.php @@ -0,0 +1,157 @@ +<?php +// $Id: drush.api.php,v 1.8 2010/06/10 11:29:13 weitzman Exp $ + +/** + * @file + * Documentation of the Drush API. + * + * All drush commands are invoked in a specific order, using + * drush-made hooks, very similar to the Drupal hook system. See drush_invoke() + * for the actual implementation. + * + * For any command named "hook", the following hooks are called, in + * order: + * + * 1. drush_hook_COMMAND_validate() + * 2. drush_hook_pre_COMMAND() + * 3. drush_hook_COMMAND() + * 4. drush_hook_post_COMMAND() + * + * For example, here are the hook opportunities for a mysite.drush.inc file + * that wants to hook into the `pm-download` command. + * + * 1. drush_mysite_pm_download_validate() + * 2. drush_mysite_pre_pm_download() + * 3. drush_mysite_pm_download() + * 4. drush_mysite_post_pm_download() + * + * If any of those fails, the rollback mechanism is called. It will + * call, in reverse, all _rollback hooks. The mysite command file can implement + * the following rollback hooks: + * + * 1. drush_mysite_post_pm_download_rollback() + * 2. drush_mysite_pm_download_rollback() + * 3. drush_mysite_pre_pm_download_rollback() + * 4. drush_mysite_pm_download_validate_rollback() + * + * Before any command is called, hook_drush_init() is also called. + * hook_drush_exit() is called at the very end of command invocation. + * + * @see includes/command.inc + * + * @see hook_drush_init() + * @see drush_hook_COMMAND_validate() + * @see drush_hook_pre_COMMAND() + * @see drush_hook_COMMAND() + * @see drush_hook_post_COMMAND() + * @see drush_hook_post_COMMAND_rollback() + * @see drush_hook_COMMAND_rollback() + * @see drush_hook_pre_COMMAND_rollback() + * @see drush_hook_COMMAND_validate_rollback() + * @see hook_drush_exit() + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Take action before any command is run. Logging an error stops command execution. + */ +function hook_drush_init() { + +} + +/** + * Run before a specific command executes. + * + * Logging an error stops command execution, and the rollback function (if any) + * for each hook implementation is invoked. + * + * @see drush_hook_COMMAND_validate_rollback() + */ +function drush_hook_COMMAND_validate() { + +} + +/** + * Run before a specific command executes. Logging an error stops command execution. + * + * Logging an error stops command execution, and the rollback function (if any) + * for each hook implementation is invoked, in addition to the + * validate rollback. + * + * @see drush_hook_pre_COMMAND_rollback() + * @see drush_hook_COMMAND_validate_rollback() + */ +function drush_hook_pre_COMMAND() { + +} + +/** + * Implementation of the actual drush command. + * + * This is where most of the stuff should happen. + * + * Logging an error stops command execution, and the rollback function (if any) + * for each hook implementation is invoked, in addition to pre and + * validate rollbacks. + * + * @see drush_hook_COMMAND_rollback() + * @see drush_hook_pre_COMMAND_rollback() + * @see drush_hook_COMMAND_validate_rollback() + */ +function drush_hook_COMMAND() { + +} + +/** + * Run after a specific command executes. Logging an error stops command execution. + * + * Logging an error stops command execution, and the rollback function (if any) + * for each hook implementation is invoked, in addition to pre, normal + * and validate rollbacks. + * + * @see drush_hook_post_COMMAND_rollback() + * @see drush_hook_COMMAND_rollback() + * @see drush_hook_pre_COMMAND_rollback() + * @see drush_hook_COMMAND_validate_rollback() + */ +function drush_hook_post_COMMAND() { + +} + +/** + * Take action after any command is run. + */ +function hook_drush_exit() { + +} + +/** + * Take action after a project has been downloaded. + */ +function hook_drush_pm_post_download($project, $release, $destination) { + +} + +/** + * Take action after a project has been updated. + */ +function hook_pm_post_update($release_name, $release_candidate_version, $project_parent_path) { + +} + +/** + * Adjust the location that a project should be downloaded to. + */ +function hook_drush_pm_adjust_download_destination(&$project, $release) { + if ($some_condition) { + $project['project_install_location'] = '/path/to/install/to/' . basename($project['full_project_path']); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/drush/drush.bat b/sites/all/modules/drush/drush.bat new file mode 100644 index 00000000..d2583a98 --- /dev/null +++ b/sites/all/modules/drush/drush.bat @@ -0,0 +1,3 @@ +@echo off +REM See http://drupal.org/node/506448 for more information. +@php.exe "%~dp0drush.php" %* diff --git a/sites/all/modules/drush/drush.info b/sites/all/modules/drush/drush.info new file mode 100644 index 00000000..f7d8a992 --- /dev/null +++ b/sites/all/modules/drush/drush.info @@ -0,0 +1,7 @@ +drush_version=3.2-dev + +; Information added by drupal.org packaging script on 2010-07-11 +version = "All-versions-3.x-dev" +project = "drush" +datestamp = "1278834030" + diff --git a/sites/all/modules/drush/drush.php b/sites/all/modules/drush/drush.php new file mode 100755 index 00000000..bd76c59d --- /dev/null +++ b/sites/all/modules/drush/drush.php @@ -0,0 +1,218 @@ +#!/usr/bin/env php +<?php +// $Id: drush.php,v 1.86 2010/02/21 05:33:28 weitzman Exp $ + +/** + * @file + * drush is a PHP script implementing a command line shell for Drupal. + * + * @requires PHP CLI 5.2.0, or newer. + */ +// Terminate immediately unless invoked as a command line script +if (!drush_verify_cli()) { + die('drush.php 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) { + die('Your command line PHP installation is too old. Drush requires at least PHP ' . DRUSH_MINIMUM_PHP . "\n"); +} + +define('DRUSH_BASE_PATH', dirname(__FILE__)); + + +define('DRUSH_REQUEST_TIME', microtime(TRUE)); + +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'; + +drush_set_context('argc', $GLOBALS['argc']); +drush_set_context('argv', $GLOBALS['argv']); + +set_error_handler('drush_error_handler'); + +exit(drush_main()); + +/** + * 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)); +} + +/** + * The main Drush function. + * + * - Parses the command line arguments, configuration files and environment. + * - Prepares and executes a Drupal bootstrap, if possible, + * - Dispatches the given command. + * + * @return + * Whatever the given command returns. + */ +function drush_main() { + $phases = _drush_bootstrap_phases(); + $completed_phases = array(); + + $return = ''; + $command_found = FALSE; + + foreach ($phases as $phase) { + if (drush_bootstrap($phase)) { + $completed_phases[$phase] = TRUE; + $command = drush_parse_command(); + + // Process a remote command if 'remote-host' option is set. + if (drush_remote_command()) { + $command_found = TRUE; + break; + } + + if (is_array($command)) { + if (array_key_exists($command['bootstrap'], $completed_phases) && empty($command['bootstrap_errors'])) { + drush_log(dt("Found command: !command (commandfile=!commandfile)", array('!command' => $command['command'], '!commandfile' => $command['commandfile'])), 'bootstrap'); + $command_found = TRUE; + // Dispatch the command(s). + $return = drush_dispatch($command); + + if (drush_get_context('DRUSH_DEBUG')) { + 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, we have not found either a valid or matching 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.", array('!args' => $args))); + } + else { + // This can occur if we get an error during _drush_bootstrap_drush_validate(); + drush_set_error('DRUSH_COULD_NOT_EXECUTE', dt("Drush could not execute.")); + } + } + + // We set this context to let the shutdown function know we reached the end of drush_main(); + drush_set_context("DRUSH_EXECUTION_COMPLETED", TRUE); + + // After this point the drush_shutdown function will run, + // exiting with the correct exit code. + return $return; +} + +/** + * 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)) { + // 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 could not be completed.")); + } + + $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')) { + drush_backend_output(); + } + elseif (drush_get_context('DRUSH_QUIET')) { + 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(); + } + + // 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); +} + +/** + * 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)); + } + else { + $message = dt('Could not login with user account `%user\'.', array('%user' => $drush_user)); + } + return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message); + } + + return TRUE; +} diff --git a/sites/all/modules/drush/drush_logo-black.png b/sites/all/modules/drush/drush_logo-black.png new file mode 100644 index 0000000000000000000000000000000000000000..4275941baf78d4c54af294b2cfe5babe330f2d09 GIT binary patch literal 23280 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 BDxClT literal 0 HcmV?d00001 diff --git a/sites/all/modules/drush/examples/example.aliases.drushrc.php b/sites/all/modules/drush/examples/example.aliases.drushrc.php new file mode 100644 index 00000000..91f87b3b --- /dev/null +++ b/sites/all/modules/drush/examples/example.aliases.drushrc.php @@ -0,0 +1,152 @@ +<?php + +/** + * Example of valid statements for an alias file. Use this + * file as a guide to creating your own aliases. + * + * Aliases are commonly used to define short names for + * local or remote Drupal installations; however, an alias + * is really nothing more than a collection of option sets. + * A canonical alias named "dev" that points to a local + * Drupal site named "dev.mydrupalsite.com" looks like this: + * + * $aliases['dev'] = array( + * 'root' => '/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 + * + * 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. If 'alias-path' is not set, then in one of the default + * locations: + * a. /etc/drush + * b. In the drush installation folder + * c. Inside the 'aliases' folder in the drush installation folder + * d. $HOME/.drush + * 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 + * + * 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 canoncial 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. + * + * 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 + * + * 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 + * + * See 'drush help site-alias' for more options for displaying site + * aliases. + * + * Although most aliases will contain only a few options, a number + * of settings that are commonly used appear below: + * + * - 'uri': This should always be the same as the site's folder name + * in the 'sites' folder. + * - '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. 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. + * - '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': The path to the folder where drush is stored. Optional; + * defaults to the folder containing the running script. Always be sure + * to set '%drush' if the path to drush is different on the remote server. + * '%drush-script': The path to the 'drush' script (used by backend invoke); + * default is 'drush' on remote machines, or the full path to drush.php on + * the local machine. Note that you only need to define one of '%drush' + * or '%drush-script', as drush can infer one from the other. + * '%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. + * + * 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', +# 'path-aliases' => array( +# '%drush' => '/path/to/drush', +# '%drush-script' => '/path/to/drush/drush', +# '%dump' => '/path/to/live/sql_dump.sql', +# '%files' => 'sites/mydrupalsite.com/files', +# '%custom' => '/my/custom/path', +# ), +# ); +#$aliases['dev'] = array( +# 'uri' => 'dev.mydrupalsite.com', +# 'root' => '/path/to/drupal/root', +# ); +#$aliases['server'] = array( +# 'remote-host' => 'mystagingserver.myisp.com', +# 'remote-user' => 'publisher', +# ); +#$aliases['live'] = array( +# 'parent' => '@server,@dev', +# 'uri' => 'mydrupalsite.com', +# ); diff --git a/sites/all/modules/drush/examples/example.drushrc.php b/sites/all/modules/drush/examples/example.drushrc.php new file mode 100644 index 00000000..936e9c39 --- /dev/null +++ b/sites/all/modules/drush/examples/example.drushrc.php @@ -0,0 +1,168 @@ +<?php +// $Id: example.drushrc.php,v 1.8 2010/06/05 15:18:16 weitzman Exp $ + +/* + * Examples of valid statements for a drushrc.php file. Use this file to cut down on + * typing of options and avoid mistakes. + * + * Rename this file to drushrc.php and optionally copy it to one of + * five convenient places, listed below in order of precedence: + * + * 1. Drupal site folder (e.g sites/{default|example.com}/drushrc.php). + * 2. Drupal installation root. + * 3. In any location, as specified by the --config (-c) option. + * 4. User's .drush folder (i.e. ~/.drush/drushrc.php). + * 5. System wide configuration folder (e.g. /etc/drush/drushrc.php). + * 6. Drush installation folder. + * + * If a configuration file is found in any of the above locations, it + * will be loaded and merged with other configuration files in the + * search list. + * + * IMPORTANT NOTE on configuration file loading: + * + * At its core, 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. + * + * Configuration files are loaded in the reverse order they are + * shown above. Configuration files #6 through #3 are loaded immediately; + * the configuration file stored in the Drupal root is loaded + * when Drupal is initialized, and the configuration file stored + * in the site folder is loaded when the site is initialized. + * + * This load order means that in a multi-site environment, the + * configuration file stored in the site folder will only be + * available for commands that operate on that one particular + * site. Additionally, there are some drush commands such as + * pm-download do not bootstrap a drupal environment at all, + * and therefore only have access to configuration files #6 - #3. + * The drush commands 'rsync' and 'sql-sync' are special cases. + * These commands will load the configuration file for the site + * specified by the source parameter; however, they do not + * load the configuration file for the site specified by the + * destination parameter, nor do they load configuration files + * for remote sites. + */ + +// DEPRECATED: Allow command names to contain spaces. +// This feature will be removed shortly; drush-3 will +// require commands to be named with dashes instead of +// spaces (e.g. "cache-clear" instead of "cache clear"). +// During the transition period, uncomment the line below +// to allow commands with spaces to be used. +# $options['allow-spaces-in-commands'] = 1; + +// Specify a particular multisite. +# $options['l'] = 'http://example.com/subir'; + +// Specify your Drupal core base directory (useful if you use symlinks). +# $options['r'] = '/home/USER/workspace/drupal-6'; + +// Load a drushrc.php configuration file from the current working directory. +# $options['c'] = '.'; + +// Specify CVS for checkouts +# $options['package-handler'] = 'cvs'; + +// Specify CVS credentials for checkouts (requires --package-handler=cvs) +# $options['cvscredentials'] = 'name:password'; + +// Specify additional directories to search for *.drush.inc files +// Use POSIX path separator (':') +# $options['i'] = 'sites/default:profiles/myprofile'; + +// Specify additional directories to search for *.alias.drushrc.php +// and *.aliases.drushrc.php files +# $options['alias-path'] = '/path/to/aliases:/path2/to/more/aliases'; + +// Specify directory where sql-sync will store persistent dump files. +// Keeping the dump files around will improve the performance of rsync +// when the database is rsync'ed to a remote system. If a dump directory +// is not specified, then sql-sync will store dumps in temporary files. +# $options['dump-dir'] = '/path/to/dumpdir'; + +// Enable verbose mode. +# $options['v'] = 1; + +// Show database passwords in 'status' and 'sql-conf' commands +# $options['show-passwords'] = 1; + +// Default logging level for php notices. Defaults to "notice"; set to "warning" +// if doing drush development. Also make sure that error_reporting is set to E_ALL +// in your php configuration file. See 'drush status' for the path to your php.ini file. +# $options['php-notices'] = 'warning'; + +// Specify options to pass to ssh in backend invoke. (Default is to prohibit password authentication; uncomment to change) +# $options['ssh-options'] = '-o PasswordAuthentication=no'; + +/* +* The output charset suitable to pass to iconv PHP function as out_charset +* parameter. Drush will convert its output from UTF-8 to the charset specified +* here. It is possible to use //TRANSLIT and //IGNORE charset name suffixes +* (see iconv documentation). If not defined conversion will not be performed. +*/ +# $options['output_charset'] = 'ISO-8859-1'; +# $options['output_charset'] = 'KOI8-R//IGNORE'; +# $options['output_charset'] = 'ISO-8859-1//TRANSLIT'; + +/* + * Customize this associative array with your own tables. This is the list of + * tables whose *data* is skipped by the 'sql-dump' and 'sql-sync' commands when + * a structure-tables-key is provided. You may add new tables to the existing + * array or add a new element. + */ +$options['structure-tables'] = array( + 'common' => array('cache', 'cache_filter', 'cache_menu', 'cache_page', 'history', 'sessions', 'watchdog'), +); + +/* + * Customize this associative array with your own tables. This is the list of + * tables that are entirely omitted by the 'sql-dump' and 'sql-sync' commands + * when a skip-tables-key is provided. This is useful if your database contains + * non Drupal tables used by some other application or during a migration for + * example. You may add new tables to the existing array or add a new element. + */ +$options['skip-tables'] = array( + 'common' => array('migration_data1', 'migration_data2'), +); + +/* + * Command-specific options + * + * To define options that are only applicable to certain commands, + * make an entry in the 'command-specific' structures as shown below. + * The name of the command may be either the command's full name + * or any of the command's aliases. + * + * Options defined here will be overridden by options of the same + * name on the command line. Unary flags such as "--verbose" are overridden + * via special "--no-xxx" options (e.g. "--no-verbose"). + * + * Limitation: If 'verbose' is set in a command-specific option, + * it must be cleared by '--no-verbose', not '--no-v', and visa-versa. + */ +# $command_specific['rsync'] = array('verbose' => TRUE); +# $command_specific['dl'] = array('cvscredentials' => 'user:pass'); + +// Specify additional directories to search for scripts +// Use POSIX path separator (':') +# $command_specific['script']['script-path'] = 'sites/all/scripts:profiles/myprofile/scripts'; + +/** + * Variable overrides: + * + * To override specific entries in the 'variable' table for this site, + * set them here. Any configuration setting from the 'variable' + * table can be given a new value. We use the $override global here + * to make sure that changes from settings.php can not wipe out these + * settings. + * + * Remove the leading hash signs to enable. + */ +# $override = array( +# 'site_name' => 'My Drupal site', +# 'theme_default' => 'minnelli', +# 'anonymous' => 'Visitor', +# ); diff --git a/sites/all/modules/drush/examples/sandwich.drush.inc b/sites/all/modules/drush/examples/sandwich.drush.inc new file mode 100644 index 00000000..03071431 --- /dev/null +++ b/sites/all/modules/drush/examples/sandwich.drush.inc @@ -0,0 +1,121 @@ +<?php +// $Id: sandwich.drush.inc,v 1.1 2010/04/22 19:37:56 weitzman Exp $ + +/** + * @file + * Example drush command. + * + * To run this *fun* command, execute `sudo drush --include=./examples mmas` + * from within your drush directory. + * + * Shows how to make your own drush command. + * + * You can copy this file to any of the following + * 1. A .drush folder in your HOME folder. + * 2. Anywhere in a folder tree below an active module on your site. + * 3. /usr/share/drush/commands (configurable) + * 4. In an arbitrary folder specified with the --include option. + */ + +/** + * Implementation of hook_drush_command(). + * + * In this hook, you specify which commands your + * drush module makes available, what it does and + * description. + * + * Notice how this structure closely resembles how + * you define menu hooks. + * + * @See drush_parse_command() for a list of recognized keys. + * + * @return + * An associative array describing your command(s). + */ +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 make-me-a-sandwich turkey --spreads=ketchup,mustard', + ), + 'aliases' => array('mmas'), + '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 <name-of-your-command>' + * + * @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."); + } +} + +/* + * Implementation of drush_hook_COMMAND_validate(). + */ +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.')); + } +} + +/** + * 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"). + * 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. + * + */ +function drush_sandwich_make_me_a_sandwich($filling = 'ascii') { + $str_spreads = ''; + 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"); + drush_print(file_get_contents(dirname(__FILE__) . '/sandwich.txt')); +} diff --git a/sites/all/modules/drush/examples/sandwich.txt b/sites/all/modules/drush/examples/sandwich.txt new file mode 100644 index 00000000..6c4c3097 --- /dev/null +++ b/sites/all/modules/drush/examples/sandwich.txt @@ -0,0 +1,24 @@ +[0;5;37;47m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .[0m +[0;5;37;47m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .[0m +[0;5;37;47m . . . . . . . . . . .:[0;1;37;47m8 [0;1;30;47m;t;;t;;;;:.[0;1;37;47m.:;%SX88[0;5;37;47m8@X%t;.. . . . [0m +[0;5;37;47m . . .. . . . . . .[0;1;37;47m%[0;1;30;47mt%;[0;1;37;47m%[0;5;37;47m@%%%%%%%%%%X@88[0;1;37;47m88XS%t;.[0;1;30;47m..:;ttt%[0;5;37;47mX. .[0m +[0;5;37;47m . . . . . . . . .X[0;5;33;40m:[0;5;37;47m8%X%%%XS%%%%%%%XS%%%%%@%%%%X%%%@%S[0;5;37;40m8[0;1;30;47m8[0;5;37;47m . [0m +[0;5;37;47m . . . . . . . . X[0;1;30;47m@[0;5;33;40m [0;5;37;47m@%%%X[0;5;1;33;47m8[0;5;37;47mX%%%[0;5;1;33;47m8[0;5;37;47m8%%%[0;5;1;33;47m8[0;5;37;47mX%%%%%%%%%X[0;1;33;47mX[0;1;37;47mt[0;1;33;47m@[0;1;30;47m8[0;37;43m@[0;1;30;43m8[0;33;47m8[0;1;30;47m@[0;5;37;47m. .[0m +[0;5;37;47m . . . . . . . .[0;1;37;47mt[0;1;30;47m@t[0;5;37;47mS;%%[0;5;1;33;47m8[0;5;37;47mXSX%[0;5;1;33;47m@[0;5;37;47mXSX%[0;5;1;33;47m@[0;5;37;47mXSS%[0;5;1;33;47m8[0;5;37;47m@[0;5;1;33;47m@[0;5;37;47m8[0;5;1;33;47m8[0;5;37;47mX[0;5;1;33;47m@[0;5;37;47m8[0;33;47m8[0;1;31;43m8[0;33;47mX8[0;1;33;47mSS[0;1;37;47m [0;1;33;47mS[0;5;33;40m;[0;1;37;47mS[0;5;37;47m. [0m +[0;5;37;47m . . . . . . .@[0;1;30;47m%X[0;1;37;47mS[0;5;37;47m%%%%%S8[0;5;1;33;47m@[0;5;37;47mX%[0;5;1;33;47m@[0;5;37;47m8%[0;5;1;33;47mX[0;5;37;47mXSS[0;5;1;33;47mX[0;5;37;47mX%S@SSSX8[0;33;47m8[0;1;31;43m8[0;1;37;47m.;[0;1;33;47m@[0;1;37;47m [0;1;33;47m8[0;5;37;41m8[0;1;30;43m8[0;35;41m@[0;1;37;47m [0;5;37;47m. . [0m +[0;5;37;47m . . . . . :[0;1;37;47m.[0;1;30;47m8:[0;5;37;47mS%%%XS[0;5;1;33;47m8[0;5;37;47mX[0;5;1;33;47m@[0;5;37;47m@X%S@SSS[0;5;1;33;47mS[0;5;37;47m8SX[0;5;1;33;47mS[0;5;37;47mX[0;5;1;33;47mX[0;5;37;47mX%[0;5;1;33;47mX[0;5;37;47m8[0;1;33;47m8[0;5;33;41mX[0;1;37;47m:;[0;5;37;43m@[0;1;31;41m8[0;5;31;41m@[0;1;31;41m:[0;33;41mS[0;5;33;41m [0;5;33;43m [0;1;31;41m8[0;5;33;40m8[0;1;37;47mS[0;5;37;47m. .[0m +[0;5;37;47m . . . .[0;1;37;47m8[0;1;30;47m%S[0;1;37;47m8[0;5;37;47m%%%%%%[0;5;1;33;47m8[0;5;37;47m@SSSX[0;5;1;33;47mX[0;5;37;47mX[0;5;1;33;47mS[0;5;37;47mX[0;5;1;33;47mS[0;5;37;47mXSX[0;5;1;33;47mS[0;5;37;47mXSSS8[0;5;1;33;47mS[0;5;37;47m8[0;1;31;47m8[0;1;31;43m8[0;1;37;47m :[0;37;43m@[0;33;41m%[0;35;41m:[0;1;31;41m%[0;30;41mX[0;33;41mX[0;1;33;43m:[0;31;43m%[0;33;42m8[0;1;32;42m%[0;30;42m:[0;5;31;40mX[0;5;37;47m: [0m +[0;5;37;47m . . .[0;1;37;47m:[0;1;30;47m8 [0;5;37;47m%%%%@%%[0;5;1;33;47m8[0;5;37;47m@S%%[0;5;1;33;47mX[0;5;37;47mXSXSSS[0;5;1;33;47mS[0;5;37;47m8[0;5;1;33;47mS[0;5;37;47m@X[0;5;1;33;47m%[0;5;37;47mX[0;5;1;33;47mS[0;5;37;47mX[0;5;1;33;47mX[0;5;37;41m8[0;33;47m8[0;1;37;47m ;[0;37;43m@[0;5;31;41mX[0;1;30;41m;[0;30;41mS[0;33;41mX[0;31;43m8[0;1;31;43m8[0;31;43mX[0;33;42m8;[0;1;30;42m%[0;5;37;40mX[0;1;30;47m8[0;1;30;40m8[0;5;37;47mt. [0m +[0;5;37;47m . . [0;1;37;47m8[0;5;37;40m8[0;1;37;47mS[0;5;37;47m%S%%%%[0;5;1;33;47m8[0;5;37;47mXSSXS[0;5;1;33;47mX[0;5;37;47m@@[0;5;1;33;47mS[0;5;37;47m@[0;5;1;33;47m%[0;5;37;47mX[0;5;1;33;47mS[0;5;37;47m8@SS[0;5;1;33;47m%[0;5;37;47m@S[0;5;1;33;47m%[0;5;37;47m8[0;5;37;41m8[0;33;47m8[0;1;37;47m [0;5;37;43m8[0;5;37;41m8[0;33;41m@[0;1;31;41mS[0;33;41m:[0;31;40m8[0;1;33;43m.[0;5;33;41m [0;1;30;43m.[0;33;42m;[0;30;42m.[0;32;40m@[0;5;33;40m%[0;5;37;40mX[0;5;33;40m:[0;33;47m@[0;5;33;40m8[0;5;37;47m;. [0m +[0;5;37;47m . . [0;5;33;40m [0;5;37;40m8[0;33;47m8[0;5;33;40m.[0;33;47m88888[0;1;30;47m8[0;1;33;47m8[0;33;47m@X[0;1;33;47mX[0;1;37;47m [0;5;37;47m888888[0;5;1;33;47m%[0;5;37;47mX[0;5;1;33;47m%[0;5;37;47m@X[0;5;1;33;47mX[0;1;37;47m [0;5;37;41m8[0;33;47m8[0;1;33;47mSS[0;5;37;41m8[0;1;30;41m@[0;30;41m@[0;1;30;41m;[0;33;41mS[0;31;43m@[0;1;31;43m8[0;1;30;43m.[0;1;32;42m%;[0;5;33;40m8[0;1;30;47m@[0;1;33;47mS%%[0;1;37;47m:[0;33;47m8[0;5;35;40m [0;5;37;47m .[0m +[0;5;37;47m . . [0;5;33;40m [0;1;33;47mS%[0;1;37;47m:[0;5;37;43m8[0;1;37;47m [0;1;33;47m@SSS[0;1;35;47mS[0;5;37;43m8[0;1;37;47m [0;1;33;47m@[0;1;31;47m@[0;5;37;43m8[0;1;31;47m@[0;5;37;43m8[0;5;33;41m [0;1;33;47m8[0;5;33;41m [0;1;33;47m8[0;5;37;41m8[0;1;31;47m888[0;1;33;47m8[0;5;37;41m8[0;1;31;47m8[0;1;33;47m@%S[0;5;35;41m:[0;1;30;41m8[0;1;31;41m:[0;31;40mS[0;1;31;43m8[0;5;33;43m [0;5;31;41m@[0;1;30;43m.[0;1;32;42m.%[0;1;30;42mS[0;5;33;40m [0;1;33;47mSX[0;33;47mX8[0;1;30;43m8[0;1;30;47m8[0;5;37;47m8;. .[0m +[0;5;37;47m . . [0;1;30;47m%[0;5;33;40m:[0;37;43m8[0;5;33;41mS[0;1;30;47m8[0;5;37;41m8[0;33;47m8[0;1;31;47m8@[0;1;33;47m8[0;1;31;47m8[0;1;33;47mS[0;1;31;47mX[0;1;33;47mS[0;1;37;47m [0;1;33;47mS[0;1;37;47m [0;1;33;47mS[0;1;37;47m::X[0;1;33;47m@[0;1;37;47m.[0;5;37;43m8[0;1;37;47m.[0;5;37;43m8[0;1;37;47m [0;1;33;47mX%[0;1;31;47mS[0;5;33;41m%[0;35;41m8[0;1;30;41mX:[0;33;41mX[0;1;31;43m88[0;1;30;43m.[0;1;32;42m.[0;30;42m%[0;5;33;40m [0;33;47m@@[0;5;33;40m.[0;1;30;47mS[0;1;37;47m.%[0;5;37;47m% .;. . [0m +[0;5;37;47m . .X[0;1;30;47mX[0;5;32;40m8[0;30;41m@[0;5;31;41m8;;%%t;;;;[0;30;41m:[0;1;31;41m@[0;5;31;41mX@888888@[0;5;33;41m8[0;1;31;41m8[0;30;41m8[0;1;30;43m8[0;1;31;43m88[0;1;33;43m.[0;1;31;43m8[0;32;43m8[0;1;32;42mS;[0;5;32;40m8[0;1;30;47m:[0;1;33;47m8[0;5;35;40m [0;1;37;47m [0;5;37;47m... . . .[0m +[0;5;37;47m . . 8[0;5;33;40m.[0;33;42m;[0;30;42m;[0;31;43m@[0;1;31;43m8[0;33;41m@[0;31;40m8[0;30;41m:[0;1;31;41m%%%%%t[0;30;41m.8[0;31;40m@[0;30;41m%[0;1;31;41mttX@8[0;33;41m@[0;1;31;41m@[0;35;41m@[0;1;30;41mS[0;31;40m8[0;1;30;43m%[0;5;1;33;41m8[0;5;33;43m [0;33;41mX[0;32;43m8[0;1;32;42mS;[0;1;30;42mX[0;5;33;40m:[0;33;47m@[0;5;33;40m;[0;1;37;47m [0;5;37;47m:... . . . . .[0m +[0;5;37;47m . t[0;5;37;40mS[0;33;42m:[0;1;32;42m8[0;31;40m@[0;31;43m;[0;1;31;43m88[0;1;33;43m.[0;5;33;41m;[0;31;43m:8[0;33;41m88[0;1;31;41m8[0;31;43mX8[0;5;33;41mS[0;5;33;43m:[0;5;33;41m.[0;1;33;43mt[0;31;43mX8[0;33;41m888[0;1;31;41m8[0;31;43mX[0;1;31;43m88[0;5;33;43m [0;5;31;41m [0;31;43mS[0;30;42m8[0;33;42mt[0;1;30;42mS[0;5;33;40mt[0;1;30;47mS[0;33;47m8[0;1;30;47m8[0;1;37;47m [0;5;37;47m:.. . . . . . . [0m +[0;5;37;47m .:[0;5;32;40mX[0;1;32;42m;;[0;1;30;42m:[0;33;42mt[0;1;30;42m%[0;33;42m;tt[0;30;42m%8[0;32;40m8[0;1;30;42m8[0;30;42mS@[0;31;40m8[0;33;42mX[0;30;42mS[0;33;42m88[0;31;43m8[0;32;43m@[0;31;43m8[0;1;30;43m.:tt[0;31;43m@[0;1;30;43m;[0;33;41m88[0;1;32;42m.[0;1;30;42mt[0;5;33;40mX[0;1;30;47mX[0;1;33;47mX[0;33;47m8[0;5;33;40m:[0;1;37;47m:[0;5;37;47m:... . . . . . . .[0m +[0;5;37;47m .:[0;1;37;47mX8S[0;5;33;40mt[0;1;37;47m:[0;1;33;47m8[0;33;47mSX[0;1;33;47mS[0;1;37;47m [0;1;33;47mXS[0;33;47m8@[0;1;33;47mX[0;1;37;47m [0;33;47m8[0;5;33;40m.[0;33;47m8[0;5;37;40m%[0;1;30;47m8[0;1;30;43m8[0;1;30;47m8[0;5;33;40m%X8[0;1;30;42m@@X[0;5;33;40m8[0;5;32;40m8[0;5;33;40mt[0;33;47mX[0;1;33;47mS[0;33;47m8[0;5;37;40mt[0;1;37;47m;[0;5;37;47m . . . . . . . . . [0m +[0;5;37;47m . [0;5;33;40m:[0;1;30;47m8[0;1;30;43m8[0;1;30;47m8[0;37;43m8[0;5;35;40m.[0;1;31;43m8[0;1;30;47m8[0;1;30;43m8[0;1;30;47m8[0;33;47m888[0;1;31;47m8[0;1;33;47mX[0;33;47m@[0;1;33;47m@X[0;1;37;47m [0;1;33;47m@[0;1;37;47m [0;1;33;47mX[0;1;37;47m [0;1;33;47mX%S%[0;1;37;47m;[0;1;33;47m8[0;1;30;47m8[0;5;37;40m;[0;5;37;47m8t .. . . . . . . . .[0m +[0;5;37;47m ... ..: . .@@888[0;1;37;47m%St [0;1;30;47m@[0;1;37;47m [0;1;30;47m@[0;1;37;47m [0;1;30;47m8SS[0;1;37;47m [0;5;37;47m8:; . . . . . . . . . . .[0m +[0;5;37;47m . . . ..::. ..:;;::::. ... . . . . . . . . . . . .[0m +[0;5;37;47m .. . . . . .. . . . . .. . . . . . . . . . . . . . [0m diff --git a/sites/all/modules/drush/includes/backend.inc b/sites/all/modules/drush/includes/backend.inc new file mode 100644 index 00000000..4ef04fa0 --- /dev/null +++ b/sites/all/modules/drush/includes/backend.inc @@ -0,0 +1,476 @@ +<?php +// $Id: backend.inc,v 1.30 2010/04/22 17:05:52 weitzman Exp $ + +/** + * @file Drush backend API + * + * When a drush command is called with the --backend option, + * it will buffer all output, and instead return a JSON encoded + * string containing all relevant information on the command that + * was just executed. + * + * Through this mechanism, it is possible for Drush commands to + * invoke each other. + * + * There are many cases where a command might wish to call another + * command in its own process, to allow the calling command to + * intercept and act on any errors that may occur in the script that + * was called. + * + * A simple example is if there exists an 'update' command for running + * update.php on a specific site. The original command might download + * a newer version of a module for installation on a site, and then + * run the update script in a separate process, so that in the case + * of an error running a hook_update_n function, the module can revert + * to a previously made database backup, and the previously installed code. + * + * By calling the script in a separate process, the calling script is insulated + * from any error that occurs in the called script, to the level that if a + * php code error occurs (ie: misformed file, missing parenthesis, whatever), + * it is still able to reliably handle any problems that occur. + * + * This is nearly a RESTful API. @see http://en.wikipedia.org/wiki/REST + * + * Instead of : + * http://[server]/[apipath]/[command]?[arg1]=[value1],[arg2]=[value2] + * + * It will call : + * [apipath] [command] --[arg1]=[value1] --[arg2]=[value2] --backend + * + * [apipath] in this case will be the path to the drush.php file. + * [command] is the command you would call, for instance 'status'. + * + * GET parameters will be passed as options to the script. + * POST parameters will be passed to the script as a JSON encoded associative array over STDIN. + * + * Because of this standard interface, Drush commands can also be executed on + * external servers through SSH pipes, simply by prepending, 'ssh username@server.com' + * in front of the command. + * + * If the key-based ssh authentication has been set up between the servers, this will just + * work, otherwise the user will be asked to enter a password. + */ + +/** + * Identify the JSON encoded output from a command. + */ +define('DRUSH_BACKEND_OUTPUT_DELIMITER', 'DRUSH_BACKEND_OUTPUT_START>>>%s<<<DRUSH_BACKEND_OUTPUT_END'); + +function drush_backend_set_result($value) { + if (drush_get_context('DRUSH_BACKEND')) { + drush_set_context('BACKEND_RESULT', $value); + } +} + +function drush_backend_get_result() { + return drush_get_context('BACKEND_RESULT'); +} + +function drush_backend_output() { + $data = array(); + + $data['output'] = ob_get_contents(); + 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(); + + // Return the options that were set at the end of the process. + $data['context'] = drush_get_merged_options(); + if (!drush_get_context('DRUSH_QUIET')) { + printf(DRUSH_BACKEND_OUTPUT_DELIMITER, json_encode($data)); + } +} + +/** + * 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. + * + * @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, $integrate = TRUE) { + $regex = sprintf(DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)'); + + preg_match("/$regex/s", $string, $match); + + if ($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 ($output) { + $data = json_decode($output, TRUE); + if (is_array($data)) { + if ($integrate) { + _drush_backend_integrate($data); + } + return $data; + } + } + return $string; +} + +/** + * Integrate log messages and error statuses into the current process. + * + * Output produced by the called script will be printed, errors will be set + * and log messages will be logged locally. + * + * @param data + * The associative array returned from the external command. + */ +function _drush_backend_integrate($data) { + if (is_array($data['log'])) { + foreach($data['log'] as $log) { + if (!is_null($log['error'])) { + drush_set_error($log['error'], $log['message']); + } + else { + drush_log($log['message'], $log['type'], $log['error']); + } + } + } + // Output will either be printed, or buffered to the drush_backend_output command. + if (drush_cmp_error('DRUSH_APPLICATION_ERROR')) { + drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output']))); + } + else { + print ($data['output']); + } + +} + +/** + * Call an external command using proc_open. + * + * @param cmd + * The command to execute. This command already needs to be properly escaped. + * @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_proc_open($cmd, $data = NULL, $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 + 2 => array("pipe", "w") // stderr is a pipe the child will write to + ); + + $process = proc_open($cmd, $descriptorspec, $pipes, null, null, array('context' => $context)); + if (is_resource($process)) { + if ($data) { + fwrite($pipes[0], json_encode($data)); // pass the data array in a JSON encoded string + } + + $info = stream_get_meta_data($pipes[1]); + stream_set_blocking($pipes[1], TRUE); + stream_set_timeout($pipes[1], 1); + $string = ''; + while (!feof($pipes[1]) && !$info['timed_out']) { + $string .= fgets($pipes[1], 4096); + $info = stream_get_meta_data($pipes[1]); + flush(); + }; + + $info = stream_get_meta_data($pipes[2]); + stream_set_blocking($pipes[2], TRUE); + stream_set_timeout($pipes[2], 1); + while (!feof($pipes[2]) && !$info['timed_out']) { + $string .= fgets($pipes[2], 4096); + $info = stream_get_meta_data($pipes[2]); + flush(); + }; + + fclose($pipes[0]); + fclose($pipes[1]); + fclose($pipes[2]); + $code = proc_close($process); + return array('cmd' => $cmd, 'output' => $string, 'code' => $code); + } + return false; +} + +/** + * Invoke a drush backend command. + * + * @param command + * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'. + * @param data + * Optional. An array containing options to pass to the call. Common options would be 'uri' if you want to call a command + * on a different site, or 'root', if you want to call a command using a different Drupal installation. + * Array items with a numeric key are treated as optional arguments to the command. + * @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. + * @param integrate + * Optional. Defaults to TRUE. + * If TRUE, any error statuses or log messages 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. + * @param drush_path + * 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 hostname + * Optional. A remote host to execute the drush command on. + * @param username + * Optional. Defaults to the current user. If you specify this, you can choose which module to send. + * + * @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($command, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL) { + $args = explode(" ", $command); + $command = array_shift($args); + return drush_backend_invoke_args($command, $args, $data, $method, $integrate, $drush_path, $hostname, $username); +} + +/* + * A variant of drush_backend_invoke() which specifies command and arguments separately. + */ +function drush_backend_invoke_args($command, $args, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL) { + $cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username); + return _drush_backend_invoke($cmd, $data, $integrate); +} + +/** + * 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 data + * An associative array to pass to the remote script. + * @param integrate + * Integrate data from remote script with local process. + * + * @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($cmd, $data = null, $integrate = TRUE) { + drush_log(dt('Running: !cmd', array('!cmd' => $cmd)), 'command'); + $proc = _drush_proc_open($cmd, $data); + + if (($proc['code'] == DRUSH_APPLICATION_ERROR) && $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'], $integrate); + if (is_array($values)) { + return $values; + } + else { + return 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 FALSE; +} + +/** + * Generate a command to execute. + * + * @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 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. + * @param integrate + * Optional. Defaults to TRUE. + * If TRUE, any error statuses or log messages 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. + * @param drush_path + * 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 hostname + * Optional. A remote host to execute the drush command on. + * @param username + * Optional. Defaults to the current user. If you specify this, you can choose which module to send. + * + * @return + * A text string representing a fully escaped command. + */ +function _drush_backend_generate_command($command, $args, &$data, $method = 'GET', $drush_path = null, $hostname = null, $username = null) { + if (drush_is_local_host($hostname)) { + $hostname = null; + } + + $drush_path = !is_null($drush_path) ? $drush_path : (is_null($hostname) ? DRUSH_COMMAND : 'drush'); // Call own drush.php file on local machines, or 'drush' on remote machines. + $data['root'] = array_key_exists('root', $data) ? $data['root'] : drush_get_context('DRUSH_DRUPAL_ROOT'); + $data['uri'] = array_key_exists('uri', $data) ? $data['uri'] : drush_get_context('DRUSH_URI'); + + $option_str = _drush_backend_argument_string($data, $method); + foreach ($data as $key => $arg) { + if (is_numeric($key)) { + $args[] = $arg; + unset($data[$key]); + } + } + foreach ($args as $arg) { + $command .= ' ' . escapeshellarg($arg); + } + // @TODO: Implement proper multi platform / multi server support. + $cmd = escapeshellcmd($drush_path) . " " . $option_str . " " . $command . " --backend"; + + if (!is_null($hostname)) { + $username = (!is_null($username)) ? $username : get_current_user(); + $ssh_options = drush_get_option('ssh-options', "-o PasswordAuthentication=no"); + $cmd = "ssh " . $ssh_options . " " . escapeshellarg($username) . "@" . escapeshellarg($hostname) . " " . escapeshellarg($cmd); + } + + return $cmd; +} + +/** + * A small utility function to call a drush command in the background. + * + * Takes the same parameters as drush_backend_invoke, but forks a new + * process by calling the command using system() and adding a '&' at the + * end of the command. + * + * Use this if you don't care what the return value of the command may be. + */ +function drush_backend_fork($command, $data, $drush_path = null, $hostname = null, $username = null) { + $data['quiet'] = TRUE; + $args = explode(" ", $command); + $command = array_shift($args); + $cmd = "(" . _drush_backend_generate_command($command, $args, $data, 'GET', $drush_path, $hostname, $username) . ' &) > /dev/null'; + drush_log(dt("Forking : !cmd", array('!cmd' => $cmd))); + system($cmd); +} + +/** + * 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, $method = 'GET') { + // Named keys are options, numerically indexed keys are optional arguments. + $args = array(); + $options = array(); + + foreach ($data as $key => $value) { + if (!is_array($value) && !is_object($value) && !is_null($value) && ($value != '')) { + if (is_numeric($key)) { + $args[$key] = $value; + } + else { + $options[$key] = $value; + } + } + } + if (array_key_exists('backend', $data)) { + unset($data['backend']); + } + + $special = array('root', 'uri'); // These should be in the command line. + $option_str = ''; + foreach ($options as $key => $value) { + if (($method != 'POST') || (($method == 'POST') && in_array($key, $special))) { + $option_str .= _drush_escape_option($key, $value); + unset($data[$key]); // Remove items in the data array. + } + } + + 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) { + if ($value !== TRUE) { + $option_str = " --$key=" . escapeshellarg($value); + } + 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'); + stream_set_blocking($fp, FALSE); + $string = stream_get_contents($fp); + fclose($fp); + if (trim($string)) { + return json_decode($string, TRUE); + } + return FALSE; +} diff --git a/sites/all/modules/drush/includes/batch.inc b/sites/all/modules/drush/includes/batch.inc new file mode 100644 index 00000000..7673cf1e --- /dev/null +++ b/sites/all/modules/drush/includes/batch.inc @@ -0,0 +1,69 @@ +<?php +/** + * @file + * Drush batch API. + * + * This file contains a fork of the Drupal Batch API that has been drastically + * simplified and tailored to Drush's unique use case. + * + * The existing API is very targeted towards environments that are web accessible, + * and would frequently attempt to redirect the user which would result in the + * drush process being completely destroyed with no hope of recovery. + * + * While the original API does offer a 'non progressive' mode which simply + * calls each operation in sequence within the current process, in most + * implementations (Drupal 5 and 6), it would still attempt to redirect + * unless very specific conditions were met. + * + * When operating in 'non progressive' mode, Drush would experience the problems + * that the API was written to solve in the first place, specifically that processes + * would exceed the available memory and exit with an error. + * + * Each major release of Drupal has also had slightly different implementations + * of the batch API, and this provides a uniform interface to all of these + * implementations. + * + */ + + +/** + * Process a Drupal batch by spawning multiple Drush processes. + * + * This function will include the correct batch engine for the current + * major version of Drupal, and will make use of the drush_backend_invoke + * system to spawn multiple worker threads to handle the processing of + * the current batch, while keeping track of available memory. + * + * The batch system will process as many batch sets as possible until + * the entire batch has been completed or half of the available memory + * has been used. + * + * This function is a drop in replacement for the existing batch_process() + * function of Drupal. + * + * @param command + * The command to call for the back end process. By default this will be + * the 'backend-process' command, but some commands such as updatedb will + * have special initialization requirements, and will need to define and + * use their own command. + * + */ +function drush_backend_batch_process($command = 'batch-process') { + drush_include_engine('drupal', 'batch', drush_drupal_major_version()); + _drush_backend_batch_process($command); +} + +/** + * 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('includes/batch.inc'); + drush_include_engine('drupal', 'batch', drush_drupal_major_version()); + _drush_batch_command($id); +} diff --git a/sites/all/modules/drush/includes/command.inc b/sites/all/modules/drush/includes/command.inc new file mode 100644 index 00000000..8dab5e5c --- /dev/null +++ b/sites/all/modules/drush/includes/command.inc @@ -0,0 +1,824 @@ +<?php +// $Id: command.inc,v 1.76 2010/04/22 06:33:48 weitzman Exp $ + +/** + * @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'); + + static $arg_opts = array('c', 'h', 'u', 'r', 'l', 'i'); + + $arguments = $options = array(); + + for ($i = 1; $i < count($args); $i++) { + $opt = $args[$i]; + // Is the arg an option (starting with '-')? + if ($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))) { + if (($args[$i+1] == NULL) || ($args[$i+1] == "") || ($args[$i + 1]{0} == "-")) { + drush_set_error('DRUSH_INVALID_INPUT', "Invalid input: -$opt needs to be followed by an argument."); + } + $options[$opt] = $args[$i + 1]; + $i++; + } + else { + $options[$opt] = true; + } + } + } + // If it's not an option, it's a command. + else { + $arguments[] = $opt; + } + } + // If arguments are specified, print the help screen. + $arguments = sizeof($arguments) ? $arguments : array('help'); + + drush_set_arguments($arguments); + drush_set_context('options', $options); +} + + +/** + * 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 += array( + 'command' => $key, + 'command-hook' => $key, + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN, + 'commandfile' => $commandfile, + 'path' => dirname($path), + 'engines' => array(), // Helpful for drush_show_help(). + 'callback' => 'drush_command', + 'description' => NULL, + 'arguments' => array(), + 'options' => array(), + 'examples' => array(), + 'aliases' => array(), + 'deprecated-aliases' => array(), + 'extras' => array(), + 'core' => array(), + 'scope' => 'site', + 'drupal dependencies' => array(), + 'drush dependencies' => array(), + 'bootstrap_errors' => array(), + 'hidden' => FALSE, + ); + // If command callback is correctly named, then fix + // up the command entry so that drush_invoke will be + // called. + 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($required_command_prefix)); + $command['callback'] = 'drush_command'; + } + else { + $command['callback-required-prefix'] = $required_command_prefix; + } + } + // Enforce the no-spaces in command names rule + if ((!drush_get_option('allow-spaces-in-commands', FALSE)) && (strpos($key, ' ') !== FALSE)) { + $command['must-replace-spaces'] = TRUE; + } + // Temporary: if there is a dash in the command name, + // then make a deprecated alias that has a space in the name. + if (strpos($key, '-') !== FALSE) { + $command['deprecated-aliases'][] = str_replace('-', ' ', $key); + } + + // Collect all the commands (without filtering) so we can match non-executable + // commands, and later explain why they are not executable. + drush_enforce_requirement_bootstrap_phase($command); + drush_enforce_requirement_core($command); + drush_enforce_requirement_drupal_dependencies($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; + } + } + // Do the same operation on the deprecated aliases. + if (isset($command['deprecated-aliases']) && count($command['deprecated-aliases'])) { + foreach ($command['deprecated-aliases'] as $alias) { + $commands[$alias] = $command; + $commands[$alias]['is_alias'] = TRUE; + $commands[$alias]['deprecated'] = TRUE; + $commands[$alias]['deprecated-name'] = $alias; + if ((!drush_get_option('allow-spaces-in-commands', FALSE)) && (strpos($alias, ' ') !== FALSE)) { + $commands[$alias]['must-not-use-spaces'] = TRUE; + } + } + } + + } + } + } + + return drush_set_context('DRUSH_COMMANDS', $commands); +} + +/** + * 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 with the following entries: + * - 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 calback. + * - description: description of the command. + * - arguments: an array of arguments that are understood by the command. for help texts. + * - 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 (not yet implemented) + * + * @example + * drush_parse_command(); + * + */ +function drush_parse_command() { + $args = drush_get_arguments(); + + // Get a list of all implemented commands. + $implemented = drush_get_commands(); + + $command = FALSE; + $arguments = array(); + // Try to determine the handler for the current command. + while (!$command && count($args)) { + $part = implode(" ", $args); + if (isset($implemented[$part])) { + $command = $implemented[$part]; + } + else { + $arguments[] = array_pop($args); + } + } + + // 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(array('h', 'help'))) { + $arguments = array($command['command']); + $command = $implemented['help']; + $command['arguments'] = $arguments; + } + else { + $arguments = array_reverse($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; + drush_set_command($command); + } + return $command; +} + +/** + * Invoke drush api calls. + * + * 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. + * + * 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. + * @return + * A boolean specifying whether or not the command was successfully completed. + * + */ +function drush_invoke($command) { + drush_command_include($command); + $args = func_get_args(); + array_shift($args); + + // Generate the base name for the hook by using the + // php string translation function to convert all + // dashes and spaces in the command name to underscores. + // TODO: put this back to $hook = str_replace("-", "_", $command); + // for better readability after the allow-spaces-in-commands + // backwards-compatibility feature is removed. + $hook = strtr($command, "- ", "__"); // n.b. str tr, not str str. + $list = drush_commandfile_list(); + + $functions = array(); + // First we build a list of functions that are about to be executed + $variations = array($hook . "_validate", "pre_$hook", $hook, "post_$hook"); + $all_available_hooks = array(); + foreach ($variations as $var_hook) { + foreach ($list as $commandfile => $filename) { + $oldfunc = sprintf("drush_%s_%s", $commandfile, $var_hook); + $func = str_replace('drush_' . $commandfile . '_' . $commandfile, 'drush_' . $commandfile, $oldfunc); + if (($oldfunc != $func) && (function_exists($oldfunc))) { + drush_log(dt("The drush command hook naming conventions have changed; the function !oldfunc must be renamed to !func. The old function will be called, but this will be removed shortly.", array('!oldfunc' => $oldfunc, '!func' => $func)), "error"); + // TEMPORARY: Allow the function to be called by its old name. + $functions[] = $oldfunc; + } + if (function_exists($func)) { + $functions[] = $func; + $all_available_hooks[] = $func . ' [*]'; + } + else { + $all_available_hooks[] = $func; + } + } + } + // If no hook functions were found, print a warning. + if (empty($functions)) { + drush_log(dt("No hook functions were found for !command.", array('!command' => $command)), 'warning'); + drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'warning'); + } + elseif (drush_get_option('show-invoke')) { + drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'internals'); + } + $rollback = FALSE; + $completed = array(); + $available_rollbacks = array(); + foreach ($functions as $func) { + $available_rollbacks[] = $func . '_rollback'; + if ($rollback === FALSE) { + $completed[] = $func; + if (function_exists($func)) { + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + if (drush_get_error()) { + drush_log(dt('An error occurred at function : @func', array('@func' => $func)), 'error'); + $rollback = TRUE; + } + } + } + } + if (drush_get_option('show-invoke')) { + drush_log(dt("Available rollback hooks for !command: !rollback", array('!command' => $command, '!rollback' => "\n" . implode("\n", $available_rollbacks))), 'internals'); + } + + // something went wrong, we need to undo + 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("Changes for $func module have been rolled back.", 'rollback'); + } + } + } + + return !$rollback; +} + + +/** + * 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'; + drush_log(dt("Initializing drush commandfile: !name", array('!name' => $name)), 'bootstrap'); + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + } + + if (!drush_get_error()) { + call_user_func_array('drush_invoke', array_merge(array($command['command-hook']), $args)); + } + + if (!drush_get_error()) { + foreach (drush_command_implements('drush_exit') as $name) { + $func = $name . '_drush_exit'; + call_user_func_array($func, $args); + _drush_log_drupal_messages(); + } + } +} + + + +/** + * Invoke a hook in all available command files that implement it. + * + * @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); +} + +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. + * - All modules in the current Drupal installation whether they are enabled or + * not. 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) { + $cache =& drush_get_context('DRUSH_COMMAND_FILES', array()); + + static $evaluated = array(); + static $deferred = array(); + + $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_option(array('i', 'include'), FALSE)) { + foreach (explode(":", $include) as $path) { + $searchpath[] = $path; + } + } + + // System commands, residing in $SHARE_PREFIX/share/drush/commands + $share_path = drush_get_context('SHARE_PREFIX', '/usr') . '/share/drush/commands'; + + if (is_dir($share_path)) { + $searchpath[] = $share_path; + } + + // User commands, residing in ~/.drush + if (!is_null(drush_server_home())) { + $searchpath[] = drush_server_home() . '/.drush'; + } + break; + case DRUSH_BOOTSTRAP_DRUPAL_SITE: + $searchpath[] = conf_path() . '/modules'; + // Too early for variable_get('install_profile', 'default'); Just use default. + $searchpath[] = "profiles/default/modules"; + // Add all module paths, even disabled modules. Prefer speed over accuracy. + $searchpath[] = 'sites/all/modules'; + break; + case DRUSH_BOOTSTRAP_DRUPAL_FULL: + // Add enabled module paths. Since we are bootstrapped, + // we can use the Drupal API. + foreach (module_list() as $module) { + $filename = drupal_get_filename('module', $module); + $searchpath[] = dirname($filename); + } + break; + } + + if (sizeof($searchpath)) { + // Build a list of all of the modules to attempt to load. + // Start with any modules deferred from a previous phase. + $list = $deferred; + + // Scan for drush command files; add to list for consideration if found. + foreach (array_unique($searchpath) as $path) { + if (is_dir($path)) { + $files = drush_scan_directory($path, '/\.drush\.inc$/'); + foreach ($files as $filename => $info) { + $module = basename($filename, '.drush.inc'); + // 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; + } + } + } + } + + // 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)) { + require_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) { + require_once(realpath($filename)); + unset($deferred[$module]); + } + 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); + } + } +} + +/** + * 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) { + $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)) { + drush_log(dt('Including !filename', array('!filename' => $filename)), 'bootstrap'); + include_once($filename); + } + } +} + +/** + * Conditionally include default options based on the command used. + */ +function drush_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['command']); + if (isset($command['aliases']) && count($command['aliases'])) { + foreach ($command['aliases'] as $alias) { + if (_drush_command_set_default_options($alias) === TRUE) { + $options_were_set = TRUE; + } + } + } + // Take the time here to clear out any options that may + // have "--no-xxx" overrides on the command line. + $commandline_options = drush_get_context('options'); + foreach ($commandline_options as $key => $value) { + if (substr($key, 0, strlen("no-") ) == "no-") { + drush_unset_option(substr($key, strlen("no-"))); + $options_were_set = TRUE; + } + } + // If we set or cleared any options, go back and re-bootstrap any global + // options such as -y and -v. + if ($options_were_set) { + _drush_bootstrap_global_options(); + } + } +} + +function _drush_command_set_default_options($command) { + $options_were_set = FALSE; + $command_default_options = drush_get_context('command-specific'); + 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 precidence than the various config file + // context, but lower than command-line options. + drush_set_option($key, $value, 'specific'); + $options_were_set = TRUE; + } + } + return $options_were_set; +} + +/** + * 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); +} + + +/** + * 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_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) { + $callback($filename); + } + } + } + } + + closedir($handle); + } + + return $files; +} + +/** + * 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 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 (empty($command['drupal dependencies'])) { + return TRUE; + } + else { + foreach ($command['drupal dependencies'] as $dependency) { + if (function_exists('module_exists') && module_exists($dependency)) { + return TRUE; + } + } + } + $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']))); +} + +/** + * Check that a command is valid for the current major version of core. + * + * @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) { + $core = $command['core']; + if (empty($core) || in_array(drush_drupal_major_version(), $core)) { + 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)); +} diff --git a/sites/all/modules/drush/includes/context.inc b/sites/all/modules/drush/includes/context.inc new file mode 100644 index 00000000..b1ab69b7 --- /dev/null +++ b/sites/all/modules/drush/includes/context.inc @@ -0,0 +1,560 @@ +<?php +// $Id: context.inc,v 1.31 2010/05/27 13:36:41 weitzman Exp $ +/** + * @file + * The Drush context API implementation. + * + * This 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. + */ + + +/** + * Return a list of the valid drush context names. + * + * These context names are carefully ordered from + * highest to lowest priority. + * + * 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. + * options : 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. + * home.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. + */ +function drush_context_names() { + static $contexts = array( + 'process', 'options', 'stdin', 'alias', 'specific', + 'custom', 'site', 'drupal', 'user', 'home.drush', 'system', + 'drush', 'default'); + + return $contexts; +} + +/** + * Return a list of possible drushrc file locations. + * + * @return + * An associative array containing possible config files to load + * The keys are the 'context' of the files, the values are the file + * system locations. + */ +function _drush_config_file($context) { + $configs = array(); + + // Did the user explicitly specify a config file? + if ($config = drush_get_option(array('c', 'config'))) { + if (is_dir($config)) { + $config = $config . '/drushrc.php'; + } + $configs['custom'] = $config; + } + + if ($site_path = drush_get_context('DRUSH_DRUPAL_SITE_ROOT')) { + $configs['site'] = $site_path . "/drushrc.php"; + } + + if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { + $configs['drupal'] = $drupal_root . '/drushrc.php'; + } + + // in the user home directory + if (!is_null(drush_server_home())) { + $configs['user'] = drush_server_home() . '/.drushrc.php'; + } + + // in $HOME/.drush directory + if (!is_null(drush_server_home())) { + $configs['home.drush'] = drush_server_home() . '/.drush/drushrc.php'; + } + + // In the system wide configuration folder. + $configs['system'] = drush_get_context('ETC_PREFIX', '') . '/etc/drush/drushrc.php'; + + // in the drush installation folder + $configs['drush'] = dirname(__FILE__) . '/../drushrc.php'; + + return empty($configs[$context]) ? '' : $configs[$context]; +} + + +/** + * Load drushrc files (if available) from several possible locations. + */ +function drush_load_config($context) { + drush_load_config_file($context, _drush_config_file($context)); +} + +function drush_load_config_file($context, $config) { + if (file_exists($config)) { + $options = $aliases = $command_specific = $override = array(); + drush_log(dt('Loading drushrc "!config" into "!context" scope.', array('!config' => 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)) { + $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()) { + global $drush_conf_override; + + // Only reset $drush_conf_override if the array is not set, otherwise keep old values and append new values to it. + if (!isset($drush_conf_override)) { + $drush_conf_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); + + // Instruct core not to store queries since we are not outputting them. + // This can be overridden by a command or a drushrc file if needed. + if (!isset($drush_conf_override['dev_query'])) { + $drush_conf_override['dev_query'] = FALSE; + } + + /** + * Allow the drushrc.php file to override $conf settings. + * This is a separate variable because the $conf array gets + * initialized to an empty array, in the drupal bootstrap process, + * and changes in settings.php would wipe out the drushrc.php settings. + */ + if (!empty($override)) { + $drush_conf_override = array_merge($drush_conf_override, $override); + } +} + +/** + * 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)) { + $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; + } + } + + // Copy site aliases and command-specific options into their + // appropriate caches. + foreach (array('site-aliases', 'command-specific') as $option_name) { + if ((isset($options)) && (array_key_exists($option_name, $options))) { + $cache =& drush_get_context($option_name); + $cache = array_merge($cache, $options[$option_name]); + unset($options[$option_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) ? $default : array(); + $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_options, 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. + * + * @param arguments + * Command line arguments, as an array. + */ +function drush_set_arguments($arguments) { + drush_set_context('arguments', $arguments); +} + +/** + * Get the arguments passed to the drush.php script. + * + * When drush_set_arguments is initially called by drush_parse_options, + * the entire list of arguments will be populated. + * Once the command has been dispatched, this will be return only the remaining + * arguments to the command. + */ +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, 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, $value = NULL, $context = NULL) { + if (array_key_exists($option, $option_overrides)) { + return $option_overrides[$option]; + } + else { + return drush_get_option($option, $value, $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; +} + +/** + * 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 ($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, "<?php\n"); + $timestamp = mktime(); + foreach ($cache as $key => $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; +} diff --git a/sites/all/modules/drush/includes/drush.inc b/sites/all/modules/drush/includes/drush.inc new file mode 100644 index 00000000..e8f1774d --- /dev/null +++ b/sites/all/modules/drush/includes/drush.inc @@ -0,0 +1,1794 @@ +<?php +// $Id: drush.inc,v 1.117 2010/06/16 12:51:23 weitzman Exp $ + +/** + * @file + * The drush API implementation and helpers. + */ + +/** + * The number of bytes in a kilobyte. Copied from Drupal. + */ +define('DRUSH_DRUPAL_KILOBYTE', 1024); + + +/** + * Dispatch a given set of commands. + * Modules can add commands by implementing hook_drush_command(). + * + * @param + * Command whose callback you want to call, defaults to current command. + */ +function drush_dispatch($command = NULL) { + $command = ($command) ? $command : drush_get_command(); + $return = FALSE; + + if ($command) { + // Add command-specific options, if applicable + drush_command_default_options($command); + + if (isset($command['must-replace-spaces'])) { + $required_name = str_replace(' ', '-', $command['command']); + drush_set_error(dt('Notice: "!name" must be renamed to "!requiredname" in order to work with this version of drush. If you are the maintainer for the module that defines this command, please rename it immediately. If you are a user of this command, you may enable spaces in commands for a while by setting "allow-spaces-in-commands" in your drush configuration file. See example.drushrc.php.', array('!name' => $command['command'], '!requiredname' => $required_name))); + return FALSE; + } + // Print a warning if someone tries to use a deprecated alias. + if (isset($command['deprecated'])) { + drush_log(dt('Warning: The command name "!deprecated" is deprecated. Please use a recommended form instead (!recommended).', array('!deprecated' => $command['deprecated-name'], '!recommended' => implode(',', array_merge(array($command['command']), $command['aliases'])))), 'warning'); + if (isset($command['must-not-use-spaces'])) { + drush_set_error(dt('You may enable spaces in commands for a while by setting "allow-spaces-in-commands" in your drush configuration file. See example.drushrc.php.', array('!name' => $command['command'], '!requiredname' => $required_name))); + return FALSE; + } + } + // Print a warning if a command callback function is misnamed + if (isset($command['callback-required-prefix'])) { + drush_log(dt('Warning: The command callback function !callback has a deprecated name. It must begin with !requiredprefix. Skipping hook functions.', array('!callback' => $command['callback'], '!requiredprefix' => $command['callback-required-prefix']))); + } + // Call the callback function of the active command. + // TODO: If we make the required prefix actually required rather than just emitting a + // warning, then this could become a direct call to drush_command (all commands with + // the required prefix will now go through drush_command + drush_invoke). + $return = call_user_func_array($command['callback'], $command['arguments']); + } + + // prevent a '1' at the end of the output + if ($return === TRUE) { + $return = ''; + } + + // Add a final log entry, just so a timestamp appears. + drush_log(dt('Command dispatch complete'), 'notice'); + + return $return; +} + +/** + * Include a file, selecting a version specific file if available. + * + * For example, if you pass the path "/var/drush" and the name + * "update" when bootstrapped on a Drupal 6 site it will first check for + * the presence of "/var/drush/update_6.inc" in include it if exists. If this + * file does NOT exist it will proceed and check for "/var/drush/update.inc". + * If neither file exists, it will return FALSE. + * + * @param $path + * The path you want to search. + * @param $name + * The file base name you want to include (not including a version suffix + * or extension). + * @param $version + * The version suffix you want to include (could be specific to the software + * or platform your are connecting to) - defaults to the current Drupal core + * major version. + * @param $extension + * The extension - defaults to ".inc". + * + * @return + * TRUE if the file was found and included. + */ +function drush_include($path, $name, $version = NULL, $extension = 'inc') { + $version = ($version) ? $version : drush_drupal_major_version(); + $file = sprintf("%s/%s_%s.%s", $path, $name, $version, $extension); + if (file_exists($file)) { + // drush_log(dt('Including version specific file : @file', array('@file' => $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; + } +} + +/** + * Return a structured array of engines of a specific type from commandfiles + * implementing hook_drush_engine_$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 $type + * The type of engine. + * + * @return + * A structured array of engines. + */ +function drush_get_engines($type) { + $engines = array(); + $list = drush_commandfile_list(); + foreach ($list as $commandfile => $path) { + if (drush_command_hook($commandfile, 'drush_engine_' . $type)) { + $function = $commandfile . '_drush_engine_' . $type; + $result = $function(); + foreach ((array)$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), $type), + ); + $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 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 unknown_type + */ +function drush_include_engine($type, $engine, $version = NULL, $path = NULL) { + $engines = drush_get_engines($type); + if (!$path && isset($engines[$engine])) { + $path = $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('!path' => $path, '!type' => $type, '!engine' => $engine))); + } + if (drush_include($path, $engine, $version)) { + 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))); +} + +/** + * Detects the version number of the current Drupal installation, + * if any. Returns false if there is no current Drupal installation, + * or it is somehow broken. + * + * This function relies on the presence of DRUPAL_ROOT/modules/system/system.module + * + * @return + * A string containing the version number of the current + * Drupal installation, if any. Otherwise, return false. + */ +function drush_drupal_version() { + static $version = FALSE; + + if (!$version) { + if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) { + if (file_exists($drupal_root . '/modules/system/system.module')) { + // We can safely include system.module as it contains defines and functions only. + require_once($drupal_root . '/modules/system/system.module'); + // We just might be dealing with an early Drupal version (pre 4.7) + if (defined('VERSION')) { + $version = VERSION; + } + } + } + } + return $version; +} + +function drush_drupal_cache_clear_all() { + $prior = drush_get_context('DRUSH_AFFIRMATIVE'); + drush_set_context('DRUSH_AFFIRMATIVE', TRUE); + drush_invoke('cache-clear', 'all'); + drush_set_context('DRUSH_AFFIRMATIVE', $prior); +} + +/** + * Returns the Drupal major version number (5, 6, 7 ...) + */ +function drush_drupal_major_version() { + $major_version = FALSE; + if ($version = drush_drupal_version()) { + $version_parts = explode('.', $version); + if (is_numeric($version_parts[0])) { + $major_version = (integer)$version_parts[0]; + } + } + return $major_version; +} + +/** + * Replace named placeholders in a WHERE snippet. + * + * Helper function to allow the usage of Drupal 7 WHERE snippets + * with named placeholders in code for Drupal 5 and 6. + * + * @param $where + * Stringwith a WHERE snippet using named placeholders. + * @param $args + * Array of placeholder values. + * @return + * String. $where filled with literals from $args. + */ +function _drush_replace_query_placeholders($where, $args) { + foreach ($args as $key => $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)) { + $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 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 5: + // In versions of Drupal <= 5, db_result only returns the first row no matter how + // many times you call it. So instead of calling it here, we use db_fetch_array which + // does increment the pointer to the next row (as db_result does on Drupal 6) + if ($array = db_fetch_array($result)) { + return array_shift($array); // return first element in array. + } + 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); +} + +/** + * 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 + * @return string + * A path to the file. + */ +function drush_save_data_to_temp_file($data) { + static $fp; + + $fp = tmpfile(); + fwrite($fp, $data); + $meta_data = stream_get_meta_data($fp); + $file = $meta_data['uri']; + drush_register_file_for_deletion($file); + + return $file; +} + +/** + * 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. + */ +function drush_tempnam($pattern, $tmp_dir = NULL) { + if ($tmp_dir == NULL) { + $tmp_dir = sys_get_temp_dir(); + } + $tmp_file = tempnam($tmp_dir, $pattern); + drush_register_file_for_deletion($tmp_file); + + return $tmp_file; +} + +/** + * 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)) { + unlink($file); + } + } +} + +/** + * Deletes the provided file or folder and + * everything inside it. + * + * @param $dir + * The directory to delete + * @return + * FALSE on failure, TRUE if everything was deleted + */ +function drush_delete_dir($dir) { + if (!file_exists($dir)) { + return TRUE; + } + if (!is_dir($dir)) { + return unlink($dir); + } + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if (!drush_delete_dir($dir.DIRECTORY_SEPARATOR.$item)) { + return FALSE; + } + } + return rmdir($dir); +} + +/** + * Move $src to $dest. If the php 'rename' function + * doesn't work, then we'll try rsync. + * + * @param $src + * The directory to move. + * @param $dest + * The destination to move the source to, including + * the new name of the folder. To move folder "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. Defaults to FALSE. + * @return + * TRUE on success, FALSE on failure + */ +function drush_move_dir($src, $dest, $overwrite = FALSE) { + // Preflight based on $overwrite if $dest exists. + if (is_dir($dest)) { + if ($overwrite) { + drush_op('drush_delete_dir', $dest); + } + else { + return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest))); + } + } + + // If rename works, then we're done. + if (@drush_op('rename', $src, $dest)) { + return TRUE; + } + // Bail if we can't make a directory at the + // destination (e.g. permissions) + if (!is_dir($dest) && (drush_op('mkdir', $dest) === FALSE)) { + return FALSE; + } + // If rename doesn't work, then try rsync. + $exec = 'rsync -raz --remove-source-files ' . $src . DIRECTORY_SEPARATOR . ' ' . $dest; + $rsync_result = drush_op('system', $exec); + if($rsync_result !== FALSE) { + // --remove-source-files deletes all of the files, but + // we still need to get rid of the directories. + drush_op('drush_delete_dir', $src); + return TRUE; + } + return FALSE; +} + +/** + * 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. + * + * @param $function + * The name of the function. + * @return + * The return value of the function, or TRUE if simulation mode is enabled. + */ +function drush_op($function) { + $args = func_get_args(); + array_shift($args); // Skip function name + + if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { + drush_print("Calling $function(". implode(", ", $args) .')'); + } + + if (drush_get_context('DRUSH_SIMULATE')) { + return TRUE; + } + + return call_user_func_array($function, $args); +} + +/** + * Rudimentary replacement for Drupal API t() function. + * + * @param string + * String to process, possibly with replacement item. + * @param array + * An associative array of replacement items. + * + * @return + * The processed string. + * + * @see t() + */ +function dt($string, $args = array()) { + if (function_exists('t')) { + return t($string, $args); + } + else { + if (!empty($args)) { + return strtr($string, $args); + } + else { + return $string; + } + } +} + +/** + * Get the available options for Drush for use by help page. + * + * @return + * An associative array containing the option definition as the key, and the description as the value, + * for each of the available options. + */ +function drush_get_option_help() { + // TODO: Add a hook for this, to allow other modules to add their global options + $options['-r <path>, --root=<path>'] = dt("Drupal root directory to use (default: current directory)"); + $options['-l <uri>, --uri=http://example.com'] = dt('URI of the drupal site to use (only needed in multisite environments)'); + $options['-v, --verbose'] = dt('Display extra information about the command.'); + $options['-d, --debug'] = dt('Display even more information, including internal messages.'); + $options['-q, --quiet'] = dt('Hide all output'); + $options['-y, --yes'] = dt("Assume 'yes' as answer to all prompts"); + $options['-n, --no'] = dt("Assume 'no' as answer to all prompts"); + $options['-s, --simulate'] = dt("Simulate all relevant actions (don't actually change the system)"); + $options['-i, --include'] = dt("A list of paths to search for drush commands"); + $options['-c, --config'] = dt("Specify a config file to use. See example.drushrc.php"); + $options['-u, --user'] = dt("Specify a user to login with. May be a name or a number."); + $options['-b, --backend'] = dt("Hide all output and return structured data (internal use only)."); + $options['-p, --pipe'] = dt("Emit a compact representation of the command for scripting."); + $options['--nocolor'] = dt("Suppress color highlighting on log messages."); + $options['--show-passwords'] = dt("Show database passwords in commands that display connection information."); + $options['-h, --help'] = dt("This help system."); + $options['--php'] = dt("The absolute path to your PHP intepreter, if not 'php' in the path."); + return $options; +} + +/** + * Prints out help for a given command. + */ +function drush_show_help($commands) { + $phases = _drush_bootstrap_phases(); + + $commandstring = implode(" ", $commands); + + foreach ($phases as $phase_index) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { + drush_bootstrap($phase_index); + } + if (!drush_get_error()) { + $commands = drush_get_commands(); + if (array_key_exists($commandstring, $commands)) { + $command = $commands[$commandstring]; + + // Merge in engine specific help. + foreach ($command['engines'] as $type => $description) { + $all_engines = drush_get_engines($type); + foreach ($all_engines as $name => $engine) { + $command = array_merge_recursive($command, $engine); + } + } + + if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) { + $help = array($command['description']); + } + drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80))); + drush_print(); + + // TODO: Let commands define additional sections. + $sections = array( + 'examples' => 'Examples', + 'arguments' => 'Arguments', + 'options' => 'Options', + ); + + foreach ($sections as $key => $value) { + if (!empty($command[$key])) { + drush_print(dt($value) . ':'); + foreach ($command[$key] as $name => $description) { + // '[command] is a token representing the current command. @see pm_drush_engine_version_control(). + $rows[] = array(str_replace('[command]', $commandstring, $name), dt($description)); + } + 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'])); + } + + return TRUE; + + } + } + else { + break; + } + } + return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring))); +} + +/** + * Executes a shell command. + * Output is only printed if in verbose mode. + * Output is stored and can be retrieved using drush_shell_exec_output(). + * If in simulation mode, no action is taken. + * + * @param $cmd + * The command to execute. May include placeholders used for sprintf. + * @param ... + * Values for the placeholders specified in $cmd. Each of these will be passed through escapeshellarg() to ensure they are safe to use on the command line. + * @return + * 0 if success. + */ +function drush_shell_exec($cmd) { + $args = func_get_args(); + + //do not change the command itself, just the parameters. + for ($x = 1; $x < sizeof($args); $x++) { + $args[$x] = escapeshellarg($args[$x]); + } + $command = call_user_func_array('sprintf', $args); + + if (drush_get_context('DRUSH_VERBOSE') || drush_get_context('DRUSH_SIMULATE')) { + drush_log('Executing: ' . $command); + } + + if (!drush_get_context('DRUSH_SIMULATE')) { + exec($command . ' 2>&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 0; + } +} + +/** + * 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(); +} + +/** + * 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" : ''); +} + +/** + * Prints a message with optional indentation. In general, + * drush_log($message, 'ok') is often a better choice than this function. + * That gets your confirmation message (for example) into the logs for this + * drush request. Consider that drush requests may be executed remotely and + * non interactively. + * + * @param $message + * The message to print. + * @param $indent + * The indentation (space chars) + */ +function drush_print($message = '', $indent = 0) { + $msg = str_repeat(' ', $indent) . (string)$message . "\n"; + if ($charset = drush_get_option('output_charset') && function_exists('iconv')) { + $msg = iconv('UTF-8', $charset, $msg); + } + print $msg; +} + +/** + * Stores a message which is printed during drush_shutdown() if in compact mode. + * @param $message + * The message to print. If $message is an array, + * then each element of the array is printed on a + * separate line. + */ +function drush_print_pipe($message = '') { + $buffer = &drush_get_context('DRUSH_PIPE_BUFFER' , ''); + if (is_array($message)) { + $message = implode("\n", $message) . "\n"; + } + $buffer .= $message; +} + +/** + * Prints an array or string. + * @param $array + * The array to print. + */ +function drush_print_r($array) { + print_r($array); +} + +/** + * Ask the user a basic yes/no question. + * + * @param $msg The question to ask + * @return TRUE if the user entered 'y', FALSE if he entered 'n' + */ +function drush_confirm($msg, $indent = 0) { + print str_repeat(' ', $indent) . (string)$msg . " (y/n): "; + + // Automatically accept confirmations if the --yes argument was supplied. + if (drush_get_context('DRUSH_AFFIRMATIVE')) { + print "y\n"; + return TRUE; + } + // Automatically cancel confirmations if the --no argument was supplied. + elseif (drush_get_context('DRUSH_NEGATIVE')) { + print "n\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; + } + print str_repeat(' ', $indent) . (string)$msg . " (y/n): "; + } +} + +/** + * 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') { + print dt($prompt) . "\n"; + + drush_print(' [0] : Cancel'); + $selection_number = 0; + foreach ($options as $key => $option) { + if ((substr($key, 0, 3) == '-- ') && (substr($key, -3) == ' --')) { + drush_print(" ".$option); + continue; + } + $selection_number++; + $message = dt($label, array('!number' => $selection_number, '!key' => $key, '!value' => $option)); + drush_print(dt(" [!number] : !message", array('!number' => $selection_number, '!message' => $message))); + $selection_list[$selection_number] = $key; + } + + while ($line = trim(fgets(STDIN))) { + if (array_key_exists($line, $selection_list)) { + return $selection_list[$line]; + } + } + drush_print(dt('Cancelled')); + return FALSE; +} + +/** + * 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() + * + * @see drush_confirm() + */ +function drush_prompt($prompt, $default = NULL) { + if (!is_null($default)) { + $prompt .= " [" . $default . "]"; + } + $prompt .= ": "; + + print $prompt; + + if (drush_get_context('DRUSH_AFFIRMATIVE')) { + return $default; + } + + $stdin = fopen('php://stdin', 'r'); + stream_set_blocking($stdin, TRUE); + while (($line = fgets($stdin)) !== FALSE) { + $line = trim($line); + if ($line === "") { + $line = $default; + } + if ($line) { + break; + } + print $prompt; + } + fclose($stdin); + return $line; +} + +/** + * Print a formatted table. + * + * @param $rows + * The rows to print. + * @param $header + * If TRUE, the first line will be treated as table header and therefore be + * underlined. + * @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. + */ +function drush_print_table($rows, $header = FALSE, $widths = array()) { + $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); + drush_print($tbl->getTable()); + 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; +} + +/** + * @defgroup dispatching Command dispatching functions. + * @{ + * + * These functions manage parameter and option manipulation + * for calls to drush backend invoke. + */ + +/** + * Process commands that are executed on a remote drush instance. + * + * @return + * TRUE if the command was handled remotely. + */ +function drush_remote_command() { + // 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_process_site_alias + $remote_host = drush_get_option('remote-host'); + if (isset($remote_host)) { + + $args = drush_get_arguments(); + $command = array_shift($args); + $remote_user = drush_get_option('remote-user'); + + drush_do_command_redispatch($command, $args, $remote_host, $remote_user); + 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); + } + $args = drush_get_arguments(); + + if (!drush_get_context('DRUSH_SIMULATE')) { + drush_print(dt("You are about to execute '!command' 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_die('Aborting.'); + } + } + $command = array_shift($args); + + foreach ($site_list as $site_spec) { + $values = drush_do_site_command(_drush_sitealias_get_record($site_spec), $command, $args); + drush_print($values['output']); + } + 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) { + drush_die('Aborting.'); + } + } + + $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_backend_invoke_args($command, array($source_spec . $source_path, $target_spec . $destination_path), $data, 'GET', TRUE); + drush_log(dt('Backend invoke is complete')); + } + } + + return $is_multiple_command; +} + +function drush_do_site_command($site_record, $command, $args = array(), $data = array(), $integrate = FALSE) { + $values = NULL; + if (!empty($site_record)) { + foreach ($site_record as $key => $value) { + if (!isset($data[$key]) && !in_array($key, drush_sitealias_site_selection_keys())) { + $data[$key] = $site_record[$key]; + } + } + + $drush_path = NULL; + if (array_key_exists('path-aliases', $site_record)) { + if (array_key_exists('%drush-script', $site_record['path-aliases'])) { + $drush_path = $site_record['path-aliases']['%drush-script']; + } + } + + $values = drush_backend_invoke_args($command, $args, $data, 'GET', $integrate, $drush_path, array_key_exists('remote-host', $site_record) ? $site_record['remote-host'] : NULL, array_key_exists('remote-user', $site_record) ? $site_record['remote-user'] : NULL); + } + return $values; +} + +/** + * 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) { + $data = drush_redispatch_get_options(); + + // If the path to drush was supplied, then pass it to backend invoke. + 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'; + } + } + } + // Call through to backend invoke. + drush_log(dt('Begin redispatch via backend invoke')); + $values = drush_backend_invoke_args($command, $args, $data, 'GET', TRUE, $drush_path, $remote_host, $remote_user); + drush_log(dt('Backend invoke is complete')); + + return $values; +} + +/** + * Get the options for this command. + * + * This function returns an array that contains all of the options + * that are appropriate for forwarding along to backend invoke. + * Pass the result from this function to backend invoke in the $data + * parameter when doing a redispatch. + */ +function drush_redispatch_get_options() { + // Start off by taking everything from the site alias and command line + // ('options' context) + $alias_context = array_diff_key(drush_get_context('alias'), array_flip(drush_sitealias_site_selection_keys())); + $options = array_merge($alias_context, drush_get_context('options')); + 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; + } + } + } + // 'php', if needed, will be included in DRUSH_COMMAND. If DRUSH_COMMAND + // is not used (e.g. when calling a remote instance of drush), then --php + // should not be passed along. + unset($options['php']); + + return $options; +} + +/** + * @} End of "defgroup dispatching". + */ + +/** + * @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; + 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) { + ob_start(); + print_r($object); + $contents = ob_get_contents(); + ob_end_clean(); + + drush_log($contents); +} + +/* + * Display the pipe output for the current request. + */ +function drush_pipe_output() { + $pipe = drush_get_context('DRUSH_PIPE_BUFFER'); + drush_print_r($pipe); +} + +/** + * 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[0;33;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' : + $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' : + $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); + 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); + } +} + +/** +* Turn drupal_set_message errors into drush_log errors +*/ +function _drush_log_drupal_messages() { + if (function_exists('drupal_get_messages')) { + + $messages = drupal_get_messages(); + + 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); + } +} + +/** + * Log Drupal watchdog() calls. + * + * A sneaky implementation of hook_watchdog(). + */ +function system_watchdog($log_entry) { + + // Transform non informative severity levels to 'error' for compatibility with _drush_print_log. + // Other severity levels are coincident with the ones we use in drush. + if (drush_drupal_major_version() >= 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']; + } + $message = strip_tags(decode_entities($message)); + // Log it. + drush_log('WD '. $log_entry['type'] . ': '.$message, $severity); +} + +/** + * 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'); + } + } + } + } +} + +/** + * @} End of "defgroup logging". + */ + +/** +* @name Error status definitions +* @{ +* Error code definitions for interpreting the current error status. +* @see drush_set_error(), drush_get_error(), drush_get_error_log(), drush_cmp_error() +*/ + +/** The command completed successfully. */ +define('DRUSH_SUCCESS', 0); +/** The command could not be completed because the framework has specified errors that have occured. */ +define('DRUSH_FRAMEWORK_ERROR', 1); +/** The command that was executed resulted in an application error, + The most commom causes for this is invalid PHP or a broken SSH + pipe when using drush_backend_invoke in a distributed manner. */ +define('DRUSH_APPLICATION_ERROR', 255); + +/** + * @} End of "name Error status defintions". + */ + +/** + * @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 <code>return drush_set_error('DRUSH_FRAMEWORK_ERROR')</code> + */ +function drush_set_error($error, $message = null) { + $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; + drush_log(($message) ? $message : $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); +} + +/** + * Turn PHP error handling off. + * + * This is commonly used while bootstrapping Drupal for install + * or updates. + */ +function drush_errors_off() { + $errors =& drush_get_context('DRUSH_ERROR_REPORTING', 0); + $errors = error_reporting(0); + ini_set('display_errors', FALSE); +} + +/** + * Turn PHP error handling on. + */ +function drush_errors_on() { + $errors =& drush_get_context('DRUSH_ERROR_REPORTING', E_ALL ^ E_NOTICE); + $errors = 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 *= 1024; + case 'm': + $value *= 1024; + case 'k': + $value *= 1024; + } + + return $value; +} + +/** + * Unset the named key anywhere in the provided + * data structure. + */ +function drush_unset_recursive(&$data, $unset_key) { + unset($data[$unset_key]); + foreach ($data as $key => $value) { + if (is_array($value)) { + drush_unset_recursive($data[$key], $unset_key); + } + } +} diff --git a/sites/all/modules/drush/includes/environment.inc b/sites/all/modules/drush/includes/environment.inc new file mode 100644 index 00000000..723e597c --- /dev/null +++ b/sites/all/modules/drush/includes/environment.inc @@ -0,0 +1,1099 @@ +<?php +// $Id: environment.inc,v 1.87 2010/06/16 23:47:23 weitzman Exp $ + +/** + * @file + * Functions used by drush to query the environment and + * setting the current configuration. + */ + +/** + * The indicator for a Drupal installation folder. + */ +define('DRUSH_DRUPAL_BOOTSTRAP', 'includes/bootstrap.inc'); + +/** + * @name Drush bootstrap phases + * @{ + * Sequential Drush bootstrapping phases. + */ + +/** + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUSH', 0); + +/** + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUPAL_ROOT', 1); + +/** + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUPAL_SITE', 2); + +/** + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION', 3); + +/** + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUPAL_DATABASE', 4); + +/** + * Fully initialize Drupal. + * + * This is the default bootstrap phase all commands will try to reach, + * unless otherwise specified. + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUPAL_FULL', 5); + +/** + * Log in to the initialiased Drupal site. + * + * This bootstrap phase is used after the site has been + * fully bootstrapped. + * + * 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. + */ +define('DRUSH_BOOTSTRAP_DRUPAL_LOGIN', 6); + +/** + * Supported version of Console Table. This is displayed in the manual install help. + */ +define('DRUSH_TABLE_VERSION', '1.1.3'); + +/** + * URL for automatic file download for supported version of Console Table. + */ +define('DRUSH_TABLE_URL', 'http://cvs.php.net/viewvc.cgi/pear/Console_Table/Table.php?revision=1.28&view=co'); + +/** + * Helper function listing phases. + * + * For commands that need to iterate through the phases, such as help + */ +function _drush_bootstrap_phases($function_names = FALSE) { + static $functions = array( + DRUSH_BOOTSTRAP_DRUSH => '_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'); + static $phases; + if ($function_names) { + return $functions; + } + if (!$phases) { + $phases = array_keys($functions); + } + return $phases; +} + +/** + * @} 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) { + 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(); + } + 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(); +} + +/** + * Validate whether a bootstrap phases 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; + 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 highest level possible, without triggering any errors. + */ +function drush_bootstrap_max() { + $phases = _drush_bootstrap_phases(); + $phase_index = DRUSH_BOOTSTRAP_DRUSH; + + // Try to bootstrap to the maximum possible level, without generating errors + foreach ($phases as $phase_index) { + if (drush_bootstrap_validate($phase_index)) { + if ($phase_index > drush_get_context('DRUSH_BOOTSTRAP_PHASE')) { + drush_bootstrap($phase_index); + } + } + else { + break; + } + } + + return $phase_index; +} + + +/** + * 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; +} + +/** + * Log PHP errors to the Drush log. This is in effect until Drupal's error + * handler takes over. + */ +function drush_error_handler($errno, $message, $filename, $line, $context) { + // If the @ error suppression operator was used, error_reporting will have + // been temporarily set to 0. + if (error_reporting() == 0) { + return; + } + + if ($errno & (E_ALL)) { + // By default we log notices. + $type = drush_get_option('php-notices', 'notice'); + + // Bitmask value that constitutes an error needing to be logged. + $error = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR; + if ($errno & $error) { + $type = 'error'; + } + + // Bitmask value that constitutes a warning being logged. + $warning = E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING; + if ($errno & $warning) { + $type = 'warning'; + } + + drush_log($message . ' ' . basename($filename) . ':' . $line, $type); + + return TRUE; + } +} + +/** + * 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; +} + +/** + * Validate initial Drush bootstrap phase. + */ +function _drush_bootstrap_drush_validate() { + // Test safe mode is off. + if (ini_get('safe_mode')) { + return drush_bootstrap_error('DRUSH_SAFE_MODE', dt('PHP safe mode is activated. Drush requires that safe mode is disabled.')); + } + + // try using the PEAR installed version of Console_Table + $tablefile = 'Console/Table.php'; + if (@file_get_contents($tablefile, FILE_USE_INCLUDE_PATH) === FALSE) { + $tablefile = DRUSH_BASE_PATH . '/includes/table.inc'; + + // Attempt to download Console Table, via various methods. + if (!file_exists($tablefile)) { + $targetpath = dirname($tablefile); + // not point continuing if we can't write to the target path + if (!is_writable($targetpath)) { + return drush_bootstrap_error('DRUSH_TABLES_INC', dt("Drush needs a copy of the PEAR Console_Table library in order to function, and the attempt to download this file automatically failed because you do not have permission to write files in !path. To continue you will need to download the !version package from http://pear.php.net/package/Console_Table, extract it, and copy the Table.php file into Drush's directory as !tablefile.", array('!path' => $targetpath, '!version' => DRUSH_TABLE_VERSION ,'!tablefile' => $tablefile))); + } + + if ($file = @file_get_contents(DRUSH_TABLE_URL)) { + @file_put_contents($tablefile, $file); + } + if (!file_exists($tablefile)) { + drush_shell_exec("wget -q --timeout=30 -O $tablefile " . DRUSH_TABLE_URL); + if (!file_exists($tablefile)) { + drush_shell_exec("curl -s --connect-timeout 30 -o $tablefile " . DRUSH_TABLE_URL); + if (!file_exists($tablefile)) { + return drush_bootstrap_error('DRUSH_TABLES_INC', 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, and copy the Table.php file into Drush's directory as !tablefile.", array('!version' => DRUSH_TABLE_VERSION ,'!tablefile' => $tablefile))); + } + } + } + } + } + require_once $tablefile; + + 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() { + // The bootstrap can fail silently, so we catch that in a shutdown function. + register_shutdown_function('drush_shutdown'); + + // 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. + if (!($columns = getenv('COLUMNS'))) { + exec('stty size 2>&1', $stty_output, $stty_status); + if (!$stty_status) $columns = preg_replace('/\d+\s(\d+)/', '$1', $stty_output[0], -1, $stty_count); + // If stty failed, or we couldn't parse it's output, we assume 80 columns. + if ($stty_status || !$stty_count) $columns = 80; + } + drush_set_context('DRUSH_COLUMNS', $columns); + + // parse the command line arguments. + drush_parse_args(); + + // statically define a way to call drush again + define('DRUSH_COMMAND', drush_find_drush()); + + $drush_info = drush_read_drush_info(); + define('DRUSH_VERSION', $drush_info['drush_version']); + + // 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. + // TODO: Note that this function will call drush_locate_root + // (from within _drush_sitealias_find_record_for_local_site), + // and drush_locate_root will be called again when bootstrapping + // the drupal root below. Is there a good way to refactor this + // so that we do not need to search for the root twice? + drush_sitealias_check_arg(); + + $backend = drush_set_context('DRUSH_BACKEND', drush_get_option(array('b', 'backend'))); + + 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); + } + } + + // Pipe implies quiet. + $quiet = drush_set_context('DRUSH_QUIET', drush_get_option(array('q', 'quiet', 'p', 'pipe'))); + + drush_set_context('DRUSH_PIPE', drush_get_option(array('p', 'pipe'))); + + // When running in backend mode, all output is buffered, and returned + // as a property of a JSON encoded associative array. + if ($backend || $quiet) { + ob_start(); + } + + _drush_bootstrap_global_options(); + + // Find any command files that are available during this bootstrap phase. + _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUSH); +} + +function _drush_bootstrap_global_options() { + // Debug implies verbose + drush_set_context('DRUSH_VERBOSE', drush_get_option(array('v', 'verbose', 'd', 'debug'), FALSE)); + drush_set_context('DRUSH_DEBUG', drush_get_option(array('d', 'debug'))); + + + // Backend implies affirmative + drush_set_context('DRUSH_AFFIRMATIVE', drush_get_option(array('y', 'yes', 'b', 'backend'), FALSE)); + drush_set_context('DRUSH_NEGATIVE', drush_get_option(array('n', 'no'), FALSE)); + drush_set_context('DRUSH_SIMULATE', drush_get_option(array('s', 'simulate'), FALSE)); + + // Suppress colored logging if --nocolor option is explicitly given or if + // terminal does not support it. + $nocolor = (drush_get_option(array('nocolor'), FALSE) || !getenv('TERM')); + if (!$nocolor) { + // Check for colorless terminal. + $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_option(array('r', 'root'), drush_locate_root()); + + if (empty($drupal_root)) { + return drush_bootstrap_error('DRUSH_NO_DRUPAL_ROOT', dt("A Drupal installation directory could not be found")); + } + if (!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); + + 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 and the DRUPAL_ROOT constant 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); + + // Save original working dir case some command wants it. + drush_set_context('DRUSH_OLDCWD', getcwd()); + + chdir($drupal_root); + drush_load_config('drupal'); + require_once(DRUPAL_ROOT . '/' . DRUSH_DRUPAL_BOOTSTRAP); + $version = drush_set_context('DRUSH_DRUPAL_VERSION', drush_drupal_version()); + $major_version = drush_set_context('DRUSH_DRUPAL_MAJOR_VERSION', drush_drupal_major_version()); + + drush_log(dt("Initialized Drupal !version root directory at !drupal_root", array("!version" => $version, '!drupal_root' => $drupal_root))); +} + +/** + * 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() { + $site_path = drush_site_path(); + $elements = explode('/', $site_path); + $current = array_pop($elements); + if (!$current) { + $current = 'default'; + } + $uri = 'http://'. $current; + + $drush_uri = drush_bootstrap_value('drush_uri', drush_get_option(array('l', 'uri'), $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, + ); + $_SERVER['HTTP_HOST'] = $drupal_base_url['host']; + 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_set_context('DRUSH_URI', drush_bootstrap_value('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')); + + // Create an alias '@self' + _drush_sitealias_cache_alias('self', array('root' => drush_get_context('DRUSH_DRUPAL_ROOT'), 'uri' => $drush_uri)); + + // Find any command files that are available during this bootstrap phase. + _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_SITE); + + drush_log(dt("Initialized Drupal site !site at !site_root", array('!site' => $site, '!site_root' => $conf_path))); + drush_load_config('site'); +} + +/** + * 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(); + _drush_bootstrap_redo_drupal_site(); +} + +/** + * Re-do the drupal site bootstrap (and possibly the + * drupal root bootstrap) if a site alias was processed + * after the site bootstrap phase completed. This will + * happen when processing "drush sitealias command" for + * a site alias defined in a drushrc.php file in the + * default site's drush configuration directory. + */ +function _drush_bootstrap_redo_drupal_site() { + // 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)) { + // Check to see if the drupal root changed. + // If it has, we will set remote-host to cause + // this command to be executed via the backend invoke + // process. + $sitealias_drupal_root = drush_get_option(array('r', 'root')); + if (($sitealias_drupal_root != null) && (DRUPAL_ROOT != $sitealias_drupal_root)) { + drush_set_option('remote-host', 'localhost'); + } + else { + // If we set an alias, then we need to bootstrap the + // drupal site once again. It is possible to re-bootstrap + // the site at this point because settings.php has not + // been included yet. + drush_log(dt("Re-bootstrap drupal site.")); + _drush_bootstrap_drupal_site_validate(); + _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. + */ +function _drush_bootstrap_drupal_configuration() { + global $conf, $drush_conf_override; + + drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + + // Overriding the $conf array from drupal CONFIGURATION bootstrap with the + // Overrides we collected on the loaded config files on DRUSH_BOOTSTRAP_DRUSH + $conf = is_array($conf) && is_array($drush_conf_override) ? array_merge($conf, $drush_conf_override) : $conf; + + // Populate the DRUSH_DB_CREDENTIALS with the fields loaded from the configuration. + $creds = array(); + switch (drush_drupal_major_version()) { + case 5: + case 6: + if (isset($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: + if (isset($GLOBALS['databases']['default']['default'])) { + $conn = $GLOBALS['databases']['default']['default']; + $creds['driver'] = $conn['driver']; + $creds['user'] = $conn['username']; + $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() { + ob_start(); + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + ob_end_clean(); + // Poke Drupal with a cluestick so it recognizes our system_watchdog() implementation. + module_implements('watchdog', FALSE, TRUE); + _drush_log_drupal_messages(); + // Find any command files that are available during this bootstrap phase. + _drush_find_commandfiles(DRUSH_BOOTSTRAP_DRUPAL_FULL); +} + +/** + * 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(array('u', 'user'), 0)); + + drush_drupal_login($drush_user); + _drush_log_drupal_messages(); +} + + +/** + * Returns the current working directory. + * + * TODO: Could cache result, but it isn't really expensive. + */ +function drush_cwd() { + // 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); + + 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. + * + * @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) { + static $site_path; + + if (!isset($site_path)) { + $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_locate_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; +} + +/** + * 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 boolean + * True if given path seems to be a Drupal root, otherwise FALSE. + */ +function drush_valid_drupal_root($path) { + return !empty($path) && is_dir($path) && file_exists($path . '/' . DRUSH_DRUPAL_BOOTSTRAP); +} + +/** + * Tests the currently loaded database credentials to ensure a database connection can be made. + */ +function drush_valid_db_credentials() { + if (class_exists('PDO')) { + $creds = drush_get_context('DRUSH_DB_CREDENTIALS'); + + $type = ($creds['driver'] == 'mysqli') ? 'mysql' : $creds['driver']; + + if (!in_array($type, PDO::getAvailableDrivers())) { + drush_log(dt('PDO support available, but the !type driver has not been installed. Assuming success.', array('!type' => $type)), 'bootstrap'); + return TRUE; + } + + $constr = sprintf("%s:dbname=%s;host=%s", $type, $creds['name'], $creds['host']); + if (!empty($creds['port'])) { + $constr .= sprintf(";port=%d", $creds['port']); + } + + try { + $db = new PDO($constr, $creds['user'], $creds['pass']); + $db = null; + return TRUE; + } + catch (PDOException $e) { + // We do not use drush_set_error here , because it's up to the calling function + // to determine whether or not this is an error or a warning. + drush_log($e->getMessage(), 'warning'); + return FALSE; + } + } + else { + drush_log(dt('PDO support not available. Could not pre-validate database credentials. Assuming success'), 'bootstrap'); + 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. + * + * We will therefore assume PHP is available in the path and is named + * "php" for execute ourselves. That is, the #!/usr/bin/env php is + * working and valid, unless a PHP constant is defined, which can be + * done by the shell wrapper. + * + * The DRUSH_COMMAND constant is initialised to the value of this + * function when environment.inc is loaded. + * + * @see DRUSH_COMMAND + */ +function drush_find_drush() { + $php = drush_get_option('php'); + if (isset($php)) { + $drush = $php . " " . realpath($_SERVER['argv'][0]) . " --php=$php"; + } else { + $drush = realpath($_SERVER['argv']['0']); + } + return $drush; +} + +/** + * 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. Note that sometimes 'uname -n' does not return the + // correct value. To fix it, put the correct hostname in /etc/hostname + // and then run 'hostname -F /etc/hostname'. + return ($host == 'localhost') || ($host == '127.0.0.1') || ($host == php_uname('n')); +} + +/** + * Get complete information for all available modules and themes. + * + * @return + * An array containing info for all available modules and themes. + */ +function drush_get_projects() { + drush_include_engine('drupal', 'environment'); + return array_merge(drush_get_modules(), drush_get_themes()); +} + +/** + * Calculate a project status based on current status and schema version. + * + * @param $project + * Array of a single project info. + * + * @return + * String describing project status. Values: enabled|disabled|not installed + */ +function drush_get_project_status($project) { + if (($project->type == 'module')&&($project->schema_version == -1)) { + $status = "not installed"; + } + else { + $status = ($project->status == 1)?'enabled':'disabled'; + } + + return $status; +} + +/** + * 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()); +} + +/** + * Return the user's home directory. + */ + +function drush_server_home() { + $home = NULL; + // $_SERVER['HOME'] isn't set on windows and generates a Notice. + if (!empty($_SERVER['HOME'])) { + $home = $_SERVER['HOME']; + } + elseif (!empty($_SERVER['HOMEDRIVE']) && !empty($_SERVER['HOMEPATH'])) { + // home on windows + $home = $_SERVER['HOMEDRIVE'] . $_SERVER['HOMEPATH']; + } + return $home; +} diff --git a/sites/all/modules/drush/includes/sitealias.inc b/sites/all/modules/drush/includes/sitealias.inc new file mode 100644 index 00000000..55ee2473 --- /dev/null +++ b/sites/all/modules/drush/includes/sitealias.inc @@ -0,0 +1,1506 @@ +<?php +// $Id: sitealias.inc,v 1.47 2010/06/08 21:33:51 greg1anderson Exp $ + +/** + * @file + * The site alias API. + * + * Run commands on remote server(s). + * @see example.drushrc.php + * @see http://drupal.org/node/670460 + */ + +/** + * Check to see if the first command-line arg or the + * -l option is a site alias; if it is, copy its record + * values to the 'alias' context. + * + * @return boolean + * TRUE if a site alias was found and processed. + */ +function drush_sitealias_check_arg() { + $args = drush_get_arguments(); + + // Test to see if the first arg is a site specification + if (_drush_sitealias_set_context_by_name($args[0])) { + array_shift($args); + // We only need to expand the site specification + // once, then we are done. + drush_set_arguments($args); + return TRUE; + } + + // Return false to indicate that no site alias was specified. + return FALSE; +} + +/** + * 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 + * An array of site specificatins. @see drush_sitealias_get_record + * @return + * An array of alias records + */ +function drush_sitealias_resolve_sitespecs($site_specifications) { + $result_list = array(); + if (!empty($site_specifications)) { + foreach ($site_specifications as $site) { + $alias_record = drush_sitealias_get_record($site); + $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record)); + } + } + return $result_list; +} + +/** + * 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: + * + * 1.) /path/to/drupal#sitename + * 2.) user@server/path/to/drupal#sitename + * 3.) user@server/path/to/drupal (sitename == server) + * 4.) user@server#sitename (only if $option['r'] set in some drushrc file on server) + * 5.) #sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites) + * 6.) 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) { + // 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); + } + 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) { + // 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 ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) { + if ((substr($alias,0,7) != 'http://') && (substr($alias,0,1) != '/')) { + // Add on a scheme so that "user:pass@server" will always parse correctly + $parsed = parse_url('http://' . $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)) { + // Load the drush config file if there is one associated with this alias + if (!isset($alias_record['remote']) && !isset($alias_record['loaded-config'])) { + $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); + + 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(':', $add_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) { + if (isset($alias_path_context)) { + return array(drush_sitealias_local_site_path($alias_path_context)); + } + else { + // 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); + + $alias_path = (array) drush_get_option('alias-path', array()); + if (empty($alias_path)) { + $alias_path[] = drush_get_context('ETC_PREFIX', '') . '/etc/drush'; + $alias_path[] = dirname(__FILE__) . '/..'; + $alias_path[] = dirname(__FILE__) . '/../aliases'; + if(!is_null(drush_server_home())) { + $alias_path[] = drush_server_home() . '/.drush'; + } + } + + return array_merge($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_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)) { + drush_log(dt('Load alias !alias', array('!alias' => $alias))); + $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); + } + } + } + + 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_alias(NULL); + 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. + * + * @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(); + + // 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_DRUPAL_ROOT'); + } + if (isset($drupal_root) && !is_array($drupal_root)) { + drush_sitealias_create_sites_alias($drupal_root); + } + } + + // 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\.drushrc\.php/'); + if ($aliasname == NULL) { + $alias_files[] = '/.*\.alias\.drushrc\.php/'; + } + else { + $alias_files[] = '/' . preg_quote($aliasname) . '\.alias\.drushrc\.php/'; + } + + // Search each path in turn + foreach ($alias_path as $path) { + // Find all of the matching files in this location + $alias_files_to_consider = array(); + 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, array('.', '..', 'CVS'), 0, FALSE))); + } + + // For every file that matches, check inside it for + // an alias with a matching name. + foreach ($alias_files_to_consider as $filename) { + if (file_exists($filename)) { + $aliases = $options = array(); + include $filename; + 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 + if (substr($filename, -20) == ".aliases.drushrc.php") { + $group_name = basename($filename,".aliases.drushrc.php"); + if (!array_key_exists($group_name, $aliases)) { + $alias_names = array(); + foreach (array_keys($aliases) as $one_alias) { + $alias_names[] = "@$one_alias"; + } + $aliases[$group_name] = array('site-list' => implode(',', $alias_names)); + } + } + // If aliasname is NULL, then we will store + // all $aliases into the alias cache + if ($aliasname == NULL) { + 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 = array_merge($result, $aliases); + } + } + // If aliasname is not NULL, then we will store + // only the named alias into the alias cache + elseif ((isset($aliases)) && array_key_exists($aliasname, $aliases)) { + drush_set_config_special_contexts($options); // maybe unnecessary + $result = array_merge($options, $aliases[$aliasname]); + _drush_sitealias_initialize_alias_record($result); + } + } + } + } + + 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 (array_key_exists('parent', $alias_value)) { + // Prevent circular references from causing an infinite loop + _drush_sitealias_cache_alias($alias_name, array()); + // Fetch and merge in each parent + foreach (explode(',', $alias_value['parent']) as $parent) { + $parent_record = drush_sitealias_get_record($parent); + if ($parent_record != NULL) { + unset($parent_record['name']); + $aliases[$alias_name] = array_merge($parent_record, $aliases[$alias_name]); + } + } + unset($aliases[$alias_name]['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'); + $cache["@$alias_name"] = $alias_record; + + // If the alias record points at a local site, make sure + // that both the drupal root 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) && array_key_exists('root', $alias_record)) { + drush_sitealias_add_to_alias_path($alias_record['root']); + $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'])) { + $values = drush_do_site_command($alias_record, "sql-conf", array(), array('db-url' => 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; +} + +/** + * 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_do_site_command($alias_record, "sql-conf", array(), array('all' => TRUE)); + if (isset($values['object'])) { + $alias_record['databases'] = $values['object']; + $altered_record = TRUE; + // 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; + } + } + } + + return $altered_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'])) { + $self_record = drush_sitealias_get_record("@self"); + if (empty($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; +} + +/** + * 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 = '') { + // 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 (substr($one_item,0,1) == '%') { + $project_array[] = substr($one_item,1); + } + } + + // If we already have a path in the path aliases, then + // there is no need to search for it remotely; we can remove + // it from the project array. + if (array_key_exists('path-aliases', $alias_record)) { + foreach ($alias_record['path-aliases'] as $key => $value) { + if (substr($key,0,1) == '%') { + unset($project_array['%' . substr($key,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)) { + // Make sure that we are bootstrapped at least to the 'site' + // level, and include file.inc to insure that we have access + // to the %file path. + if (drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) { + include_once DRUPAL_ROOT . '/includes/file.inc'; + } + $status_values = _core_site_status_table($project_list); + } + else { + $values = drush_do_site_command($alias_record, "status", array(), empty($project_list) ? array() : array('project' => $project_list)); + $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; paramter 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 = $alias_record['remote-host'] . $result; + if (array_key_exists('remote-user', $alias_record)) { + $result = $alias_record['remote-user'] . '@' . $result; + } + } + + // add the database info to the specification if desired + if ($with_db) { + $result = $result . '?db-url=' . urlencode($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 (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_BOOTSTRAP) . '/' , array('.', '..', 'CVS'), 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_BOOTSTRAP))) { + $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']) . '/'; + } + } +} + +/** + * 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_DRUPAL_ROOT'); + $drupal_root = drush_get_option(array('r', 'root'), drush_locate_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) { + return str_replace(array('http://', '/', ':'), 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. + * @return array + * An array of database values. + */ +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, + 'port' => NULL, + 'database' => NULL, + ); + $url = (object)$url; + return array( + 'driver' => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme, + 'username' => urldecode($url->user), + 'password' => urldecode($url->pass), + 'port' => urldecode($url->port), + 'host' => urldecode($url->host), + 'database' => substr(urldecode($url->path), 1), // skip leading '/' character + ); +} + +function drush_sitealias_convert_db_from_db_url($db_url) { + $result = array(); + + if (is_array($db_url)) { + $default_db = array(); + + foreach ($db_url as $db_name => $db_urlstr) { + $default_db[$db_name] = drush_convert_db_from_db_url($db_urlstr); + } + + $result['default'] = $default_db; + } + else { + $result = array('default' => array('default' => drush_convert_db_from_db_url($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); + return TRUE; + } + return FALSE; +} + +/** + * Given a site alias record, copy selected fields from it + * into the drush 'alias' context. The 'alias' context has + * lower precedence than the 'options' 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 = array('site-aliases', 'command-specific'); + // Also skip 'remote-host' and 'remote-user' if 'remote-host' is actually + // the local machine + if (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'; + } + // 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") { + foreach (array('%drush-script', '%dump', '%include') as $path_key) { + if (array_key_exists($path_key, $value)) { + $options[$prefix . substr($path_key, 1)] = $value[$path_key]; + } + } + } + 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 + $colon_pos = strpos($path, ':'); + if ($colon_pos !== FALSE) { + $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); +} + + + +/** + * 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). + * @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) { + $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)) { + $site_alias_settings = drush_sitealias_get_record($alias); + } + + if (!empty($site_alias_settings)) { + // 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'])) { + if (array_key_exists('remote-user', $site_alias_settings)) { + $remote_user = $site_alias_settings['remote-user'] . '@'; + } + $machine = $remote_user . $site_alias_settings['remote-host']; + } + 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"; + } + } + + // If the --exclude-other-sites option is specified, then + // convert that into --include-path='%site' and --exclude-sites. + if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_option_override($additional_options, 'exclude-other-sites-processed', FALSE, 'process')) { + $additional_options['include-path'] = '%site,' . drush_get_option_override($additional_options, 'include-path', ''); + $additional_options['exclude-sites'] = TRUE; + $additional_options['exclude-other-sites-processed'] = TRUE; + } + // If the --exclude-files option is specified, then + // convert that into --exclude-path='%files'. + if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) { + $additional_options['exclude-path'] = '%files,' . drush_get_option_override($additional_options, 'exclude-path', ''); + $additional_options['exclude-files-processed'] = TRUE; + } + + // 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_bootstrap_value('drush_uri', drush_get_option(array('l', '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-path', ''); + $exclude_path = drush_get_option_override($additional_options, 'exclude-path', ''); + $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(); + $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT'); + } + if (empty($drupal_root)) { + $drupal_root = ''; + } + // Add a slash to the end of the drupal root, as below. + elseif ($drupal_root[strlen($drupal_root)-1] != '/') { + $drupal_root = $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 (($value[0] != '/') && ($key != '%root')) { + $full_path_aliases[$key] = $drupal_root . $value; + } + // We do not want slashes on the end of our path aliases. + if ($value[strlen($value)-1] == '/') { + $full_path_aliases[$key] = substr($full_path_aliases[$key], -1); + } + } + + // 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-path', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path)); + } + if (!empty($exclude_path)) { + drush_set_option('exclude-path', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path)); + } + + // The path component is just the path part of the full + // machine:path specification (including the colon). + $path_component = (!empty($path) ? ':' . $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) || ($path[0] != '/')) { + $path = $drupal_root . $path; + } + + // If there is a $machine component, to the path, then + // add it to the beginning + $evaluated_path = $path; + if (!empty($machine)) { + $evaluated_path = $machine . ':' . $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'] = $path_component; + $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', 'name'); +} diff --git a/sites/all/modules/drush/includes/table.inc b/sites/all/modules/drush/includes/table.inc new file mode 100644 index 00000000..d3acfb29 --- /dev/null +++ b/sites/all/modules/drush/includes/table.inc @@ -0,0 +1,893 @@ +<?php +/** + * Utility for printing tables from commandline scripts. + * + * PHP versions 4 and 5 + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * o Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @category Console + * @package Console_Table + * @author Richard Heyes <richard@phpguru.org> + * @author Jan Schneider <jan@horde.org> + * @copyright 2002-2005 Richard Heyes + * @copyright 2006-2008 Jan Schneider + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id: Table.php,v 1.27 2008/10/20 22:22:13 yunosh Exp $ + * @link http://pear.php.net/package/Console_Table + */ + +define('CONSOLE_TABLE_HORIZONTAL_RULE', 1); +define('CONSOLE_TABLE_ALIGN_LEFT', -1); +define('CONSOLE_TABLE_ALIGN_CENTER', 0); +define('CONSOLE_TABLE_ALIGN_RIGHT', 1); +define('CONSOLE_TABLE_BORDER_ASCII', -1); + +/** + * The main class. + * + * @category Console + * @package Console_Table + * @author Jan Schneider <jan@horde.org> + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Console_Table + */ +class Console_Table +{ + /** + * The table headers. + * + * @var array + */ + var $_headers = array(); + + /** + * The data of the table. + * + * @var array + */ + var $_data = array(); + + /** + * The maximum number of columns in a row. + * + * @var integer + */ + var $_max_cols = 0; + + /** + * The maximum number of rows in the table. + * + * @var integer + */ + var $_max_rows = 0; + + /** + * Lengths of the columns, calculated when rows are added to the table. + * + * @var array + */ + var $_cell_lengths = array(); + + /** + * Heights of the rows. + * + * @var array + */ + var $_row_heights = array(); + + /** + * How many spaces to use to pad the table. + * + * @var integer + */ + var $_padding = 1; + + /** + * Column filters. + * + * @var array + */ + var $_filters = array(); + + /** + * Columns to calculate totals for. + * + * @var array + */ + var $_calculateTotals; + + /** + * Alignment of the columns. + * + * @var array + */ + var $_col_align = array(); + + /** + * Default alignment of columns. + * + * @var integer + */ + var $_defaultAlign; + + /** + * Character set of the data. + * + * @var string + */ + var $_charset = 'utf-8'; + + /** + * Border character. + * + * @var string + */ + var $_border = CONSOLE_TABLE_BORDER_ASCII; + + /** + * Whether the data has ANSI colors. + * + * @var boolean + */ + var $_ansiColor = false; + + /** + * Constructor. + * + * @param integer $align Default alignment. One of + * CONSOLE_TABLE_ALIGN_LEFT, + * CONSOLE_TABLE_ALIGN_CENTER or + * CONSOLE_TABLE_ALIGN_RIGHT. + * @param string $border The character used for table borders or + * CONSOLE_TABLE_BORDER_ASCII. + * @param integer $padding How many spaces to use to pad the table. + * @param string $charset A charset supported by the mbstring PHP + * extension. + * @param boolean $color Whether the data contains ansi color codes. + */ + function Console_Table($align = CONSOLE_TABLE_ALIGN_LEFT, + $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1, + $charset = null, $color = false) + { + $this->_defaultAlign = $align; + $this->_border = $border; + $this->_padding = $padding; + $this->_ansiColor = $color; + if ($this->_ansiColor) { + include_once 'Console/Color.php'; + } + if (!empty($charset)) { + $this->setCharset($charset); + } + } + + /** + * Converts an array to a table. + * + * @param array $headers Headers for the table. + * @param array $data A two dimensional array with the table + * data. + * @param boolean $returnObject Whether to return the Console_Table object + * instead of the rendered table. + * + * @static + * + * @return Console_Table|string A Console_Table object or the generated + * table. + */ + function fromArray($headers, $data, $returnObject = false) + { + if (!is_array($headers) || !is_array($data)) { + return false; + } + + $table = new Console_Table(); + $table->setHeaders($headers); + + foreach ($data as $row) { + $table->addRow($row); + } + + return $returnObject ? $table : $table->getTable(); + } + + /** + * Adds a filter to a column. + * + * Filters are standard PHP callbacks which are run on the data before + * table generation is performed. Filters are applied in the order they + * are added. The callback function must accept a single argument, which + * is a single table cell. + * + * @param integer $col Column to apply filter to. + * @param mixed &$callback PHP callback to apply. + * + * @return void + */ + function addFilter($col, &$callback) + { + $this->_filters[] = array($col, &$callback); + } + + /** + * Sets the charset of the provided table data. + * + * @param string $charset A charset supported by the mbstring PHP + * extension. + * + * @return void + */ + function setCharset($charset) + { + $locale = setlocale(LC_CTYPE, 0); + setlocale(LC_CTYPE, 'en_US'); + $this->_charset = strtolower($charset); + setlocale(LC_CTYPE, $locale); + } + + /** + * Sets the alignment for the columns. + * + * @param integer $col_id The column number. + * @param integer $align Alignment to set for this column. One of + * CONSOLE_TABLE_ALIGN_LEFT + * CONSOLE_TABLE_ALIGN_CENTER + * CONSOLE_TABLE_ALIGN_RIGHT. + * + * @return void + */ + function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT) + { + switch ($align) { + case CONSOLE_TABLE_ALIGN_CENTER: + $pad = STR_PAD_BOTH; + break; + case CONSOLE_TABLE_ALIGN_RIGHT: + $pad = STR_PAD_LEFT; + break; + default: + $pad = STR_PAD_RIGHT; + break; + } + $this->_col_align[$col_id] = $pad; + } + + /** + * Specifies which columns are to have totals calculated for them and + * added as a new row at the bottom. + * + * @param array $cols Array of column numbers (starting with 0). + * + * @return void + */ + function calculateTotalsFor($cols) + { + $this->_calculateTotals = $cols; + } + + /** + * Sets the headers for the columns. + * + * @param array $headers The column headers. + * + * @return void + */ + function setHeaders($headers) + { + $this->_headers = array(array_values($headers)); + $this->_updateRowsCols($headers); + } + + /** + * Adds a row to the table. + * + * @param array $row The row data to add. + * @param boolean $append Whether to append or prepend the row. + * + * @return void + */ + function addRow($row, $append = true) + { + if ($append) { + $this->_data[] = array_values($row); + } else { + array_unshift($this->_data, array_values($row)); + } + + $this->_updateRowsCols($row); + } + + /** + * Inserts a row after a given row number in the table. + * + * If $row_id is not given it will prepend the row. + * + * @param array $row The data to insert. + * @param integer $row_id Row number to insert before. + * + * @return void + */ + function insertRow($row, $row_id = 0) + { + array_splice($this->_data, $row_id, 0, array($row)); + + $this->_updateRowsCols($row); + } + + /** + * Adds a column to the table. + * + * @param array $col_data The data of the column. + * @param integer $col_id The column index to populate. + * @param integer $row_id If starting row is not zero, specify it here. + * + * @return void + */ + function addCol($col_data, $col_id = 0, $row_id = 0) + { + foreach ($col_data as $col_cell) { + $this->_data[$row_id++][$col_id] = $col_cell; + } + + $this->_updateRowsCols(); + $this->_max_cols = max($this->_max_cols, $col_id + 1); + } + + /** + * Adds data to the table. + * + * @param array $data A two dimensional array with the table data. + * @param integer $col_id Starting column number. + * @param integer $row_id Starting row number. + * + * @return void + */ + function addData($data, $col_id = 0, $row_id = 0) + { + foreach ($data as $row) { + if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { + $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE; + $row_id++; + continue; + } + $starting_col = $col_id; + foreach ($row as $cell) { + $this->_data[$row_id][$starting_col++] = $cell; + } + $this->_updateRowsCols(); + $this->_max_cols = max($this->_max_cols, $starting_col); + $row_id++; + } + } + + /** + * Adds a horizontal seperator to the table. + * + * @return void + */ + function addSeparator() + { + $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE; + } + + /** + * Returns the generated table. + * + * @return string The generated table. + */ + function getTable() + { + $this->_applyFilters(); + $this->_calculateTotals(); + $this->_validateTable(); + + return $this->_buildTable(); + } + + /** + * Calculates totals for columns. + * + * @return void + */ + function _calculateTotals() + { + if (empty($this->_calculateTotals)) { + return; + } + + $this->addSeparator(); + + $totals = array(); + foreach ($this->_data as $row) { + if (is_array($row)) { + foreach ($this->_calculateTotals as $columnID) { + $totals[$columnID] += $row[$columnID]; + } + } + } + + $this->_data[] = $totals; + $this->_updateRowsCols(); + } + + /** + * Applies any column filters to the data. + * + * @return void + */ + function _applyFilters() + { + if (empty($this->_filters)) { + return; + } + + foreach ($this->_filters as $filter) { + $column = $filter[0]; + $callback = $filter[1]; + + foreach ($this->_data as $row_id => $row_data) { + if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) { + $this->_data[$row_id][$column] = + call_user_func($callback, $row_data[$column]); + } + } + } + } + + /** + * Ensures that column and row counts are correct. + * + * @return void + */ + function _validateTable() + { + if (!empty($this->_headers)) { + $this->_calculateRowHeight(-1, $this->_headers[0]); + } + + for ($i = 0; $i < $this->_max_rows; $i++) { + for ($j = 0; $j < $this->_max_cols; $j++) { + if (!isset($this->_data[$i][$j]) && + (!isset($this->_data[$i]) || + $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) { + $this->_data[$i][$j] = ''; + } + + } + $this->_calculateRowHeight($i, $this->_data[$i]); + + if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { + ksort($this->_data[$i]); + } + + } + + $this->_splitMultilineRows(); + + // Update cell lengths. + for ($i = 0; $i < count($this->_headers); $i++) { + $this->_calculateCellLengths($this->_headers[$i]); + } + for ($i = 0; $i < $this->_max_rows; $i++) { + $this->_calculateCellLengths($this->_data[$i]); + } + + ksort($this->_data); + } + + /** + * Splits multiline rows into many smaller one-line rows. + * + * @return void + */ + function _splitMultilineRows() + { + ksort($this->_data); + $sections = array(&$this->_headers, &$this->_data); + $max_rows = array(count($this->_headers), $this->_max_rows); + $row_height_offset = array(-1, 0); + + for ($s = 0; $s <= 1; $s++) { + $inserted = 0; + $new_data = $sections[$s]; + + for ($i = 0; $i < $max_rows[$s]; $i++) { + // Process only rows that have many lines. + $height = $this->_row_heights[$i + $row_height_offset[$s]]; + if ($height > 1) { + // Split column data into one-liners. + $split = array(); + for ($j = 0; $j < $this->_max_cols; $j++) { + $split[$j] = preg_split('/\r?\n|\r/', + $sections[$s][$i][$j]); + } + + $new_rows = array(); + // Construct new 'virtual' rows - insert empty strings for + // columns that have less lines that the highest one. + for ($i2 = 0; $i2 < $height; $i2++) { + for ($j = 0; $j < $this->_max_cols; $j++) { + $new_rows[$i2][$j] = !isset($split[$j][$i2]) + ? '' + : $split[$j][$i2]; + } + } + + // Replace current row with smaller rows. $inserted is + // used to take account of bigger array because of already + // inserted rows. + array_splice($new_data, $i + $inserted, 1, $new_rows); + $inserted += count($new_rows) - 1; + } + } + + // Has the data been modified? + if ($inserted > 0) { + $sections[$s] = $new_data; + $this->_updateRowsCols(); + } + } + } + + /** + * Builds the table. + * + * @return string The generated table string. + */ + function _buildTable() + { + if (!count($this->_data)) { + return ''; + } + + $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII + ? '|' + : $this->_border; + $separator = $this->_getSeparator(); + + $return = array(); + for ($i = 0; $i < count($this->_data); $i++) { + for ($j = 0; $j < count($this->_data[$i]); $j++) { + if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE && + $this->_strlen($this->_data[$i][$j]) < + $this->_cell_lengths[$j]) { + $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j], + $this->_cell_lengths[$j], + ' ', + $this->_col_align[$j]); + } + } + + if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { + $row_begin = $rule . str_repeat(' ', $this->_padding); + $row_end = str_repeat(' ', $this->_padding) . $rule; + $implode_char = str_repeat(' ', $this->_padding) . $rule + . str_repeat(' ', $this->_padding); + $return[] = $row_begin + . implode($implode_char, $this->_data[$i]) . $row_end; + } elseif (!empty($separator)) { + $return[] = $separator; + } + + } + + $return = implode("\r\n", $return); + if (!empty($separator)) { + $return = $separator . "\r\n" . $return . "\r\n" . $separator; + } + $return .= "\r\n"; + + if (!empty($this->_headers)) { + $return = $this->_getHeaderLine() . "\r\n" . $return; + } + + return $return; + } + + /** + * Creates a horizontal separator for header separation and table + * start/end etc. + * + * @return string The horizontal separator. + */ + function _getSeparator() + { + if (!$this->_border) { + return; + } + + if ($this->_border == CONSOLE_TABLE_BORDER_ASCII) { + $rule = '-'; + $sect = '+'; + } else { + $rule = $sect = $this->_border; + } + + $return = array(); + foreach ($this->_cell_lengths as $cl) { + $return[] = str_repeat($rule, $cl); + } + + $row_begin = $sect . str_repeat($rule, $this->_padding); + $row_end = str_repeat($rule, $this->_padding) . $sect; + $implode_char = str_repeat($rule, $this->_padding) . $sect + . str_repeat($rule, $this->_padding); + + return $row_begin . implode($implode_char, $return) . $row_end; + } + + /** + * Returns the header line for the table. + * + * @return string The header line of the table. + */ + function _getHeaderLine() + { + // Make sure column count is correct + for ($j = 0; $j < count($this->_headers); $j++) { + for ($i = 0; $i < $this->_max_cols; $i++) { + if (!isset($this->_headers[$j][$i])) { + $this->_headers[$j][$i] = ''; + } + } + } + + for ($j = 0; $j < count($this->_headers); $j++) { + for ($i = 0; $i < count($this->_headers[$j]); $i++) { + if ($this->_strlen($this->_headers[$j][$i]) < + $this->_cell_lengths[$i]) { + $this->_headers[$j][$i] = + $this->_strpad($this->_headers[$j][$i], + $this->_cell_lengths[$i], + ' ', + $this->_col_align[$i]); + } + } + } + + $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII + ? '|' + : $this->_border; + $row_begin = $rule . str_repeat(' ', $this->_padding); + $row_end = str_repeat(' ', $this->_padding) . $rule; + $implode_char = str_repeat(' ', $this->_padding) . $rule + . str_repeat(' ', $this->_padding); + + $separator = $this->_getSeparator(); + if (!empty($separator)) { + $return[] = $separator; + } + for ($j = 0; $j < count($this->_headers); $j++) { + $return[] = $row_begin + . implode($implode_char, $this->_headers[$j]) . $row_end; + } + + return implode("\r\n", $return); + } + + /** + * Updates values for maximum columns and rows. + * + * @param array $rowdata Data array of a single row. + * + * @return void + */ + function _updateRowsCols($rowdata = null) + { + // Update maximum columns. + $this->_max_cols = max($this->_max_cols, count($rowdata)); + + // Update maximum rows. + ksort($this->_data); + $keys = array_keys($this->_data); + $this->_max_rows = end($keys) + 1; + + switch ($this->_defaultAlign) { + case CONSOLE_TABLE_ALIGN_CENTER: + $pad = STR_PAD_BOTH; + break; + case CONSOLE_TABLE_ALIGN_RIGHT: + $pad = STR_PAD_LEFT; + break; + default: + $pad = STR_PAD_RIGHT; + break; + } + + // Set default column alignments + for ($i = count($this->_col_align); $i < $this->_max_cols; $i++) { + $this->_col_align[$i] = $pad; + } + } + + /** + * Calculates the maximum length for each column of a row. + * + * @param array $row The row data. + * + * @return void + */ + function _calculateCellLengths($row) + { + for ($i = 0; $i < count($row); $i++) { + if (!isset($this->_cell_lengths[$i])) { + $this->_cell_lengths[$i] = 0; + } + $this->_cell_lengths[$i] = max($this->_cell_lengths[$i], + $this->_strlen($row[$i])); + } + } + + /** + * Calculates the maximum height for all columns of a row. + * + * @param integer $row_number The row number. + * @param array $row The row data. + * + * @return void + */ + function _calculateRowHeight($row_number, $row) + { + if (!isset($this->_row_heights[$row_number])) { + $this->_row_heights[$row_number] = 1; + } + + // Do not process horizontal rule rows. + if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { + return; + } + + for ($i = 0, $c = count($row); $i < $c; ++$i) { + $lines = preg_split('/\r?\n|\r/', $row[$i]); + $this->_row_heights[$row_number] = max($this->_row_heights[$row_number], + count($lines)); + } + } + + /** + * Returns the character length of a string. + * + * @param string $str A multibyte or singlebyte string. + * + * @return integer The string length. + */ + function _strlen($str) + { + static $mbstring, $utf8; + + // Strip ANSI color codes if requested. + if ($this->_ansiColor) { + $str = Console_Color::strip($str); + } + + // Cache expensive function_exists() calls. + if (!isset($mbstring)) { + $mbstring = function_exists('mb_strlen'); + } + if (!isset($utf8)) { + $utf8 = function_exists('utf8_decode'); + } + + if ($utf8 && + ($this->_charset == strtolower('utf-8') || + $this->_charset == strtolower('utf8'))) { + return strlen(utf8_decode($str)); + } + if ($mbstring) { + return mb_strlen($str, $this->_charset); + } + + return strlen($str); + } + + /** + * Returns part of a string. + * + * @param string $string The string to be converted. + * @param integer $start The part's start position, zero based. + * @param integer $length The part's length. + * + * @return string The string's part. + */ + function _substr($string, $start, $length = null) + { + static $mbstring; + + // Cache expensive function_exists() calls. + if (!isset($mbstring)) { + $mbstring = function_exists('mb_substr'); + } + + if (is_null($length)) { + $length = $this->_strlen($string); + } + if ($mbstring) { + $ret = @mb_substr($string, $start, $length, $this->_charset); + if (!empty($ret)) { + return $ret; + } + } + return substr($string, $start, $length); + } + + /** + * Returns a string padded to a certain length with another string. + * + * This method behaves exactly like str_pad but is multibyte safe. + * + * @param string $input The string to be padded. + * @param integer $length The length of the resulting string. + * @param string $pad The string to pad the input string with. Must + * be in the same charset like the input string. + * @param const $type The padding type. One of STR_PAD_LEFT, + * STR_PAD_RIGHT, or STR_PAD_BOTH. + * + * @return string The padded string. + */ + function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT) + { + $mb_length = $this->_strlen($input); + $sb_length = strlen($input); + $pad_length = $this->_strlen($pad); + + /* Return if we already have the length. */ + if ($mb_length >= $length) { + return $input; + } + + /* Shortcut for single byte strings. */ + if ($mb_length == $sb_length && $pad_length == strlen($pad)) { + return str_pad($input, $length, $pad, $type); + } + + switch ($type) { + case STR_PAD_LEFT: + $left = $length - $mb_length; + $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), + 0, $left, $this->_charset) . $input; + break; + case STR_PAD_BOTH: + $left = floor(($length - $mb_length) / 2); + $right = ceil(($length - $mb_length) / 2); + $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), + 0, $left, $this->_charset) . + $input . + $this->_substr(str_repeat($pad, ceil($right / $pad_length)), + 0, $right, $this->_charset); + break; + case STR_PAD_RIGHT: + $right = $length - $mb_length; + $output = $input . + $this->_substr(str_repeat($pad, ceil($right / $pad_length)), + 0, $right, $this->_charset); + break; + } + + return $output; + } + +} -- GitLab