first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,193 @@
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. Similarly, it runs update.php, executes sql
queries and DB migrations, and misc utilities like run cron or clear cache.
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 4 does not support Windows; see "For Windows", below.
* 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.
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
- add the folder that contains drush to your PATH
PATH=$PATH:/path/to/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 define an environment variable
DRUSH_PHP that points to the php to execute:
export DRUSH_PHP=/usr/bin/php5
OR
- If you want to exactly control how drush is called, you may define an alias
that executes the drush.php file directly and passes that path to drush:
$ alias drush='/path/to/php/php5 -d memory_limit=128M /path/to/drush/drush.php --php="/path/to/php/php5 -d memory_limit=128M"'
Note that it is necessary to pass the '--php' option to drush to define
how drush should call php if it needs to do so.
If you define an alias, 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.
NOTE ON PHP.INI FILES
- Usually, php is configured to use separate php.ini files for the web server
and the command line. To see which php.ini file drush is using, run:
$ drush status
- Compare the php.ini that drush is using with the php.ini that the webserver is
using. Make sure that drush's php.ini is given as much memory to work with as
the web server is; otherwise, Drupal might run out of memory when drush
bootstraps it.
- Drush requires a fairly unrestricted php environment to run in. In particular,
you should insure that safe_mode, open_basedir, disable_functions and
disable_classes are empty.
- If drush is using the same php.ini file as the web server, you can create
a php.ini file exclusively for drush by copying your web server's php.ini
file to the folder $HOME/.drush or the folder /etc/drush. Then you may edit
this file and change the settings described above without affecting the
php enviornment of your web server. Alternately, if you only want to
override a few values, copy example.drush.ini from the "examples" folder
into $HOME/.drush or the folder /etc/drush and edit to suit. See comments
in example.drush.ini for more details.
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:
- Drush 4 does not support Windows. Consider using Drush 3 or Drush 5 instead.
- Consider using on Linux/Unix/OSX using Virtualbox or other VM. Windows support is lacking.
- The Drush 5 Windows installer can be found at http://drush.ws/drush_windows_installer.
- Instructions for installing Drush 3 on Windows can be found at
http://drupal.org/node/594744.
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 even more documentation, use the 'topic' command:
$ drush topic
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 core-cli command provide a customized bash shell or lets you enhance
your usual shell with its --pipe option.
Many commands support a --pipe option which returns machine readable output. See
`drush pm-list --status=enabled --pipe` as an example.
Very intensive scripts can exhaust your available PHP memory. One remedy is to
just restart automatically using bash. For example:
while true; do drush search-index; sleep 5; done
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
(sandwich.drush.inc). You will also see an example 'policy' file which
can be customized to block certain commands or arguments as your organization
needs.
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 sandwich.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.
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, jonhattan.

View File

@@ -0,0 +1,311 @@
<?php
/**
* @file
* An early implementation of Site Archive dump/restore. See
* http://groups.drupal.org/site-archive-format.
*/
function archive_drush_command() {
$items['archive-dump'] = array(
'description' => 'Backup your code, files, and database into a single file.',
'arguments' => array(
'targets' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.',
),
'options' => array(
'description' => 'Describe the archive contents.',
'tags' => 'Add tags to the archive manifest. Delimit multiple by commas.',
'destination' => 'The full path and filename in which the archive should be stored. If omitted, it will be saved to the drush-backups directory and a filename will be generated.',
'overwrite' => 'Do not fail if the destination file exists; overwrite it instead.',
'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".',
'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.',
'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.',
),
'examples' => array(
'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.',
'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.',
'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
'aliases' => array('ard', 'archive-backup', 'arb'),
);
$items['archive-restore'] = array(
'description' => 'Expand a site archive into a Drupal web site.',
'arguments' => array(
'file' => 'The site archive file that should be expanded.',
'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.',
),
'options' => array(
'destination' => 'Specify where the Drupal site should be expanded. Defaults to the current working directory.',
'db-prefix' => 'An optional table prefix to use during restore.',
'db-url' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.',
'db-su' => 'Account to use when creating the new database. Optional.',
'db-su-pw' => 'Password for the "db-su" account. Optional.',
'overwrite' => 'Allow drush to overwrite any files in the destinion.',
),
'examples' => array(
'drush archive-restore ./example.tar.gz' => 'Restore the files and databases for all sites in the archive.',
'drush archive-restore ./example.tar.gz example.com' => 'Restore the files and database for example.com site.',
'drush archive-restore ./example.tar.gz --destination=/var/www/example.com/docroot' => 'Restore archive to a custom location.',
'drush archive-restore ./example.tar.gz --db-url=mysql://root:pass@127.0.0.1/dbname' => 'Restore archive to a new database (and customize settings.php to point there.).',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'aliases' => array('arr'),
);
return $items;
}
/**
* Command callback. Generate site archive file.
*/
function drush_archive_dump($sites_subdirs = '@self') {
$aliases = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs));
foreach ($aliases as $key => $alias) {
if (($db_record = sitealias_get_databases_from_record($alias))) {
$full[$key] = $alias += $db_record;
}
else {
drush_log(dt('DB connections not found for !alias', array('!alias' => $alias)), 'error');
return;
}
}
// The user can specify a destination filepath or not. That filepath might
// end with .gz, .tgz, or something else. At the end of this command we will
// gzip a file, and we want it to end up with the user-specified name (if
// any), but gzip renames files and refuses to compress files ending with
// .gz and .tgz, making our lives difficult. Solution:
//
// 1. Create a unique temporary base name to which gzip WILL append .gz.
// 2. If no destination is provided, set $dest_dir to a backup directory and
// $final_destination to be the unique name in that dir.
// 3. If a destination is provided, set $dest_dir to that directory and
// $final_destination to the exact name given.
// 4. Set $destination, the actual working file we will build up, to the
// unqiue name in $dest_dir.
// 5. After gzip'ing $destination, rename $destination.gz to
// $final_destination.
//
// Sheesh.
// Create the unique temporary name.
$date = gmdate('Ymd_his');
$first = current($full);
$prefix = count($sites_subdirs) > 1 ? 'multiple_sites' : $first['default']['default']['database'];
$temp_dest_name = "$prefix.$date.tar";
$final_destination = drush_get_option('destination');
if (!$final_destination) {
// No destination provided.
drush_include_engine('version_control', 'backup');
$backup = new drush_pm_version_control_backup();
// TODO: this standard drush pattern leads to a slightly obtuse directory structure.
$dest_dir = $backup->prepare_backup_dir('archive-dump');
if (empty($dest_dir)) {
$dest_dir = drush_tempdir();
}
$final_destination = "$dest_dir/$temp_dest_name.gz";
}
else {
// Use the supplied --destination. If it is relative, resolve it
// relative to the directory in which drush was invoked.
$command_cwd = getcwd();
drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd()));
// This doesn't perform realpath on the basename, but that's okay. This is
// not path-based security. We just use it for checking for perms later.
$dest_dir = realpath(dirname($final_destination));
$final_destination = $dest_dir . '/' . basename($final_destination);
drush_op('chdir', $command_cwd);
}
// $dest_dir is either the backup directory or specified directory. Set our
// working file.
$destination = "$dest_dir/$temp_dest_name";
// Validate the FINAL destination. It should be a file that does not exist
// (unless --overwrite) in a writable directory (and a writable file if
// it exists). We check all this up front to avoid failing after a long
// dump process.
$overwrite = drush_get_option('overwrite');
$dest_dir = dirname($final_destination);
$dt_args = array('!file' => $final_destination, '!dir' => $dest_dir);
if (is_dir($final_destination)) {
drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('destination !file must be a file, not a directory.', $dt_args));
return;
}
else if (file_exists($final_destination)) {
if (!$overwrite) {
drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('destination !file exists; specify --overwrite to overwrite.', $dt_args));
return;
}
else if (!is_writable($final_destination)) {
drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('destination !file is not writable.', $dt_args));
return;
}
}
else if (!is_writable(dirname($final_destination))) {
drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('destination directory !dir is not writable.', $dt_args));
return;
}
$docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT'));
$docroot = basename($docroot_path);
$workdir = dirname($docroot_path);
// Archive Drupal core, excluding sites dir.
drush_shell_cd_and_exec($workdir, "tar --exclude '{$docroot}/sites' --dereference -cf %s %s", $destination, $docroot);
// Add sites/all to the same archive.
drush_shell_cd_and_exec($workdir, "tar --dereference -rf %s %s", $destination, "{$docroot}/sites/all");
// Dump the database(s) for each site and add to the archive.
foreach ($full as $key => $alias) {
foreach ($alias['databases'] as $dbkey => $target) {
$tmp = drush_tempdir();
// Use a subdirectory name matching the docroot name.
drush_mkdir("{$tmp}/{$docroot}");
$result_file = "{$tmp}/{$target['default']['database']}.sql";
drush_set_option('result-file', $result_file);
$table_selection = drush_sql_get_table_selection();
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $target['default']);
drush_shell_exec($dump_exec);
drush_shell_cd_and_exec($tmp, 'tar --dereference -rf %s %s', $destination, basename($result_file));
}
}
// Build a manifest file AND add sites/$subdir to archive as we go.
$platform = array(
'datestamp' => time(),
'formatversion' => '1.0',
'generator' => drush_get_option('generator', 'Drush archive-dump'),
'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION),
'description' => drush_get_option('description', ''),
'tags' => drush_get_option('tags', ''),
);
$contents = drush_export_ini(array('Global' => $platform));
$i=0;
foreach ($full as $key => $alias) {
$status = drush_invoke_sitealias_args($alias, 'core-status', array(), array());
// Add the site specific directory to archive.
if (!empty($status['object']['%paths']['%site'])) {
drush_shell_cd_and_exec($workdir, "tar --dereference -rf %s %s", $destination, "{$docroot}/sites/" . basename($status['object']['%paths']['%site']));
}
$site = array(
'docroot' => DRUPAL_ROOT,
'sitedir' => @$status['object']['%paths']['%site'],
'files-public' => @$status['object']['%paths']['%files'],
'files-private' => @$status['object']['%paths']['%private'],
);
// Add info for each DB connection (usually only 1);
foreach ($alias['databases'] as $dbkey => $target) {
$site["database-$dbkey-file"] = './' . $target['default']['database'] . '.sql';
$site["database-$dbkey-driver"] = $target['default']['driver'];
}
// The section title is the sites subdirectory name.
$info[basename($site['sitedir'])] = $site;
$contents .= "\n" . drush_export_ini($info);
unset($info);
$i++;
}
file_put_contents("{$tmp}/MANIFEST.ini", $contents);
// Add manifest to archive.
drush_shell_cd_and_exec($tmp, 'tar --dereference -rf %s %s', $destination, "MANIFEST.ini");
// Compress the archive
drush_shell_exec("gzip --no-name -f %s", $destination);
// gzip appends .gz unless the name already ends in .gz, .tgz, or .taz.
if ("{$destination}.gz" != $final_destination) {
drush_move_dir("{$destination}.gz", $final_destination, $overwrite);
}
drush_log(dt('Archive saved to !dest', array('!dest' => $final_destination)), 'ok');
drush_print_pipe($final_destination);
return $final_destination;
}
/**
* Command callback. Restore web site(s) from a site archive file.
*/
function drush_archive_restore($file, $site_id = NULL) {
$tmp = drush_tempdir();
if (!$files = drush_tarball_extract($file, $tmp)) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp)));
}
$manifest = $tmp . '/MANIFEST.ini';
if (file_exists($manifest)) {
if (!$ini = parse_ini_file($manifest, TRUE)) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.'));
}
}
else {
// No manifest. Try to find docroot and DB dump file.
$db_file = drush_scan_directory($tmp, '/\.sql$/', array('.', '..', 'CVS'), 0, 0);
$directories = glob($tmp . '/*' , GLOB_ONLYDIR);
$ini = array(
'Global' => array(),
'default' => array(
'docroot' => reset($directories),
'sitedir' => 'sites/default',
'database-default-file' => key($db_file),
),
);
}
// Grab the first site in the Manifest and move docroot to destination.
$ini_tmp = $ini;
unset($ini_tmp['Global']);
$first = array_shift($ini_tmp);
$docroot = basename($first['docroot']);
$destination = drush_get_option('destination', realpath('.') . "/$docroot");
if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE _RESTORE_FILES', dt('Unable to restore files to !dest', array('!dest' => $destination)));
}
// Loop over sites and restore databases and append to settings.php.
foreach ($ini as $section => $site) {
if ($section != 'Global' && (is_null($site_id) || $section == $site_id) && !empty($site['database-default-file'])) {
// Restore database
$sql_file = $tmp . '/' . $site['database-default-file'];
if ($db_url = drush_get_option('db-url')) {
if (empty($site_id) && count($ini) >= 3) {
// TODO: Use drushrc to provide multiple db-urls for multi-restore?
return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.'));
}
$db_spec = drush_convert_db_from_db_url($db_url);
}
else {
$site_specification = $destination . '#' . $section;
if ($return = drush_invoke_sitealias_args($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) {
$databases = $return['object'];
$db_spec = $databases['default']['default'];
}
else {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key)));
}
}
drush_sql_empty_db($db_spec);
_drush_sql_query(NULL, $db_spec, $sql_file);
// Append new DB info to settings.php.
if ($db_url) {
$settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php';
chmod($settingsfile, 0755); // Need to do something here or else we can't write.
file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND);
if (drush_drupal_major_version() >= 7) {
file_put_contents($settingsfile, "\n" . '$databases = \'' . drush_sitealias_convert_db_from_db_url($db_url) . "';\n", FILE_APPEND);
}
else {
file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND);
}
}
}
}
return $destination;
}

View File

@@ -0,0 +1,227 @@
<?php
/**
* Implementation of hook_drush_command().
*/
function cache_drush_command() {
$items = array();
$items['cache-get'] = array(
'description' => 'Fetch a cached object and display it.',
'examples' => array(
'drush cache-get schema' => 'Display the data for the cache id "schema" from the "cache" bin.',
'drush cache-get update_available_releases update' => 'Display the data for the cache id "update_available_releases" from the "update" bin.',
),
'arguments' => array(
'cid' => 'The id of the object to fetch.',
'bin' => 'Optional. The cache bin to fetch from.',
),
'options' => array(
'format' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.',
),
'aliases' => array('cg'),
);
$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['cache-set'] = array(
'description' => 'Cache an object expressed in JSON or var_export() format.',
'arguments' => array(
'cid' => 'The id of the object to set.',
'data' => 'The object to set in the cache. Use \'-\' to read the object from STDIN.',
'bin' => 'Optional. The cache bin to store the object in.',
'expire' => 'Optional. CACHE_PERMANENT, CACHE_TEMPORARY, or a Unix timestamp.',
),
'options' => array(
'format' => 'Format to parse the object. Use "string" for string (default), and "json" for JSON.',
'cache-get' => 'If the object is the result a previous fetch from the cache, only store the value in the "data" property of the object in the cache.',
),
'aliases' => array('cs'),
);
return $items;
}
/**
* Command callback for drush cache-clear.
*/
function drush_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();
// Check if the provided type ($type) is a valid cache type.
if ($type && !key_exists($type, $types)) {
return drush_set_error(dt("'!type' cache is not a valid cache type", array('!type' => $type)));
}
if ($type) {
drush_op($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;
}
}
/**
* Print an object returned from the cache.
*
* @param $cid
* The cache ID of the object to fetch.
* @param $bin
* Optional parameter to specify a specific bin to fetch from.
*/
function drush_cache_get($cid = NULL, $bin = NULL) {
if (!$cid) {
drush_log(dt('You must specify a cache id to fetch.'), 'error');
return;
}
if (!$bin) {
$bin = 'cache';
}
$result = cache_get($cid, $bin);
if (!empty($result)) {
switch (drush_get_option('format', 'print_r')) {
case 'export':
drush_print(var_export($result, TRUE));
return;
case 'json':
drush_print(drush_json_encode($result));
return;
case 'print_r':
default:
drush_print_r($result);
return;
}
}
else {
drush_log(dt('The !cid object in the !bin cache bin was not found.', array('!cid' => $cid, '!bin' => $bin)), 'error');
}
}
/**
* Set an object in the cache.
*
* @param $cid
* The cache ID of the object to fetch.
* @param $data
* The data to save to the cache, or '-' to read from STDIN.
* @param $bin
* Optional parameter to specify a specific bin to fetch from.
* @param $expire
* Optional parameter to specify the expiry of the cached object.
*/
function drush_cache_set($cid = NULL, $data = '', $bin = NULL, $expire = CACHE_PERMANENT) {
if (!$cid) {
drush_log(dt('You must specify a cache id to set.'), 'error');
return;
}
if ($data === '') {
drush_log(dt('The data to set must be non-empty.'), 'error');
return;
}
if (!$bin) {
$bin = 'cache';
}
if ($data == '-') {
$data = stream_get_contents(STDIN);
}
// Now, we parse the object.
switch (drush_get_option('format', 'string')) {
case 'json':
$data = drush_json_decode($data);
break;
}
if (drush_get_option('cache-get')) {
$data = $data->data;
}
switch(drush_drupal_major_version()) {
case 5:
cache_set($cid, $bin, $data, $expire);
break;
case 6:
case 7:
default:
cache_set($cid, $data, $bin, $expire);
}
}
function drush_cache_clear_types() {
$types = array(
'all' => 'drupal_flush_all_caches',
'theme registry' => 'drush_cache_clear_theme_registry',
'menu' => 'menu_rebuild',
'css+js' => 'drush_cache_clear_css_js',
'block' => 'drush_cache_clear_block',
'module list' => 'drush_get_modules',
'theme list' => 'drush_get_themes',
);
if (drush_drupal_major_version() >= 7) {
$types['registry'] = 'registry_update';
}
elseif (drush_drupal_major_version() == 6 && module_exists('autoload')) {
// TODO: move this to autoload module.
$types['registry'] = 'autoload_registry_update';
}
if (count(module_implements('node_grants'))) {
$types['nodeaccess'] = 'node_access_rebuild';
}
// Include the appropriate environment engine, so callbacks can use core
// version specific cache clearing functions directly.
drush_include_engine('drupal', 'environment');
// Command files may customize $types as desired.
drush_command_invoke_all_ref('drush_cache_clear', $types);
return $types;
}
function drush_cache_clear_theme_registry() {
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();
}
/**
* Clear the cache of the block output.
*/
function drush_cache_clear_block() {
cache_clear_all(NULL, 'cache_block');
}

View File

@@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
<?php
/**
* @file
* Documentation commands providing various topics.
*/
/**
* Implementation of hook_drush_help().
*/
function docs_drush_help($section) {
switch ($section) {
case 'meta:docs:title':
return dt('Documentation commands');
case 'meta:docs:summary':
return dt('Show information on various drush topics.');
}
}
/**
* Implementation of hook_drush_command().
*
* @return
* An associative array describing your command(s).
*/
function docs_drush_command() {
$docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
//
// Topic commands.
// Any commandfile may add topics.
// Set 'topic' => TRUE to indicate the command is a topic (REQUIRED)
// Begin the topic name with the name of the commandfile (just like
// any other command).
//
$items['docs-readme'] = array(
'description' => 'README.txt',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/README.txt'),
);
$items['docs-configuration'] = array(
'description' => 'Drush configuration overview with examples',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.drushrc.php'),
);
$items['docs-aliases'] = array(
'description' => 'Site aliases overview with examples',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.aliases.drushrc.php'),
);
$items['docs-ini-files'] = array(
'description' => 'Configuring php.ini or drush.ini for use with drush.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.drush.ini'),
);
$items['docs-bootstrap'] = array(
'description' => 'Information about the drush bootstrap process.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/bootstrap.html'),
);
$items['docs-scripts'] = array(
'description' => 'Overview on how to write drush shell scripts.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/shellscripts.html'),
);
$items['docs-commands'] = array(
'description' => 'Overview on how to write drush commands.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/commands.html'),
);
$items['docs-errorcodes'] = array(
'description' => 'Summary of drush error codes.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
);
$items['docs-api'] = array(
'description' => 'Drush API',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/drush.api.php'),
);
$items['docs-context'] = array(
'description' => 'Drush contexts',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/context.html'),
);
$items['docs-examplescript'] = array(
'description' => 'Example drush script',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/helloworld.script'),
);
$items['docs-examplecommand'] = array(
'description' => 'Example drush command file.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/sandwich.drush.inc'),
);
$items['docs-policy'] = array(
'description' => 'Example policy file.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/policy.drush.inc'),
);
$items['docs-upgrading'] = array(
'description' => 'Upgrading Drupal using the drush site-upgrade command.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/upgrade.html'),
);
return $items;
}
/**
* docs-error-codes command. Print a list of all error codes
* that can be found.
*/
function drush_docs_errorcodes() {
$header = <<<EOD
==== Drush Error Codes ====
Drush error codes are alphanumeric constants that represent an unrecoverable error condition that may arise during the execution of some command. They are set by the following function:
return drush_set_error('DRUSH_ERROR_CODE', dt('Error message.'));
In general, any drush command that calls drush_set_error is expected to also return FALSE as its function result. The drush_set_error function returns FALSE to make it easy to exit with an error code. Error codes are returned as part of the drush backend invoke process, which is used by drush API functions such as drush_invoke_process. An example of how to test for a specific error code is shown below:
\$result = drush_invoke_process('some-command');
if (array_key_exists('DRUSH_ERROR_CODE', \$result['error_log'])) {
// handle ocurrances of DRUSH_ERROR_CODE here
}
Some of the available drush error codes are listed in the table below.
EOD;
// Find all of the files that we will search for error messages.
// Start with all of the commandfiles.
$commandfiles = drush_commandfile_list();
$files = array_flip($commandfiles);
// In addition to the commandfiles, we will also look for files
// that drush will load when executing a command; examples include
// updatecode.pm.inc and sync.sql.inc.
$commands = drush_get_commands();
foreach ($commands as $command_name => $command) {
$files = array_merge($files, drush_command_get_includes($command_name));
}
// We will also search through all of the .inc files in the
// drush includes directory
$drush_include_files = drush_scan_directory(DRUSH_BASE_PATH . '/includes', '/.*\.inc$/', array('.', '..', 'CVS'), 0, FALSE);
foreach ($drush_include_files as $filename => $info) {
$files[$filename] = 'include';
}
// Extract error messages from all command files
$error_list = array();
foreach ($files as $file => $commandfile) {
_drush_docs_find_set_error_calls($error_list, $file, $commandfile);
}
// Order error messages alphabetically by key
ksort($error_list);
// Convert to a table
$data = array();
foreach ($error_list as $error_code => $error_messages) {
$data[] = array($error_code, '-', implode("\n", $error_messages));
}
$tmpfile = drush_tempnam('drush-errorcodes.');
file_put_contents($tmpfile, $header);
drush_print_table($data, FALSE, array(0 => 35), $tmpfile);
drush_print_file($tmpfile);
}
/**
* Search through a php source file looking for calls to
* the function drush_set_error. If found, and if the
* first parameter is an uppercase alphanumeric identifier,
* then record the error code and the error message in our table.
*/
function _drush_docs_find_set_error_calls(&$error_list, $filename, $shortname) {
$lines = file($filename);
foreach ($lines as $line) {
$matches = array();
// Find the error code after the drush_set_error call. The error code
// should consist of uppercase letters and underscores only (numbers thrown in just in case)
$match_result = preg_match("/.*drush_set_error[^'\"]['\"]([A-Z0-9_]*)['\"][^,]*,[^'\"]*(['\"])/", $line, $matches);
if ($match_result) {
$error_code = $matches[1];
$quote_char = $matches[2];
$error_message = "";
$message_start = strlen($matches[0]) - 1;
// Regex adapted from http://stackoverflow.com/questions/1824325/regex-expression-for-escaped-quoted-string-wont-work-in-phps-preg-match-allif ($quote_char == '"') {
if ($quote_char == '"') {
$regex = '/"((?:[^\\\]*?(?:\\\")?)*?)"/';
}
else {
$regex = "/'((?:[^\\\]*?(?:\\\')?)*?)'/";
}
$match_result = preg_match($regex, $line, $matches, 0, $message_start);
if ($match_result) {
$error_message = $matches[1];
}
$error_list[$error_code] = array_key_exists($error_code, $error_list) ? array_merge($error_list[$error_code], array($error_message)) : array($error_message);
}
}
}

View File

@@ -0,0 +1,257 @@
<?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') {
global $user;
$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) {
if ($user->uid) {
$data = drush_backend_invoke($command, array($batch['id'], '-u', $user->uid));
}
else {
$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();
}
}

View File

@@ -0,0 +1,200 @@
<?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') {
global $user;
$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) {
if ($user->uid) {
$data = drush_backend_invoke($command, array($batch['id'], '-u', $user->uid));
}
else {
$data = drush_backend_invoke($command, array($batch['id']));
}
$finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
}
}
}
/**
* Initialize the batch command and call the worker function.
*
* Loads the batch record from the database and sets up the requirements
* for the worker, such as registering the shutdown function.
*
* @param id
* The batch id of the batch being processed.
*/
function _drush_batch_command($id) {
$batch =& batch_get();
// 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']);
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* @file
* Specific functions for a drupal 7 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 available modules.
*/
function drush_get_modules() {
return system_rebuild_module_data();
}
/**
* Returns drupal required modules, including modules declared as required dynamically.
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($module_info as $name => $module) {
if (isset($module->info['required']) && $module->info['required']) {
$required[] = $name;
}
}
return array_unique($required);
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$dependencies = array_reverse($module_info[$module]->requires);
$unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info));
if (!empty($unmet_dependencies)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
else {
// check for version incompatibility
foreach ($dependencies as $dependency_name => $v) {
$current_version = $module_info[$dependency_name]->info['version'];
$incompatibility = drupal_check_incompatibility($v, $current_version);
if (!is_null($incompatibility)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_VERSION_MISMATCH',
'message' => dt('Module !module cannot be enabled because it depends on !dependency !required_version but !current_version is available', array('!module' => $module, '!dependency' => $dependency_name, '!required_version' => $incompatibility, '!current_version' => $current_version))
);
}
}
}
$status[$key]['unmet-dependencies'] = $unmet_dependencies;
$status[$key]['dependencies'] = array_keys($dependencies);
}
return $status;
}
/**
* Return dependents of modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependents for each one of $modules
*/
function drush_module_dependents($modules, $module_info) {
$dependents = array();
foreach ($modules as $module) {
$dependents = array_merge($dependents, drupal_map_assoc(array_keys($module_info[$module]->required_by)));
}
return array_unique($dependents);
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// The list of modules already have all the dependencies, but they might not
// be in the correct order. Still pass $enable_dependencies = TRUE so that
// Drupal will enable the modules in the correct order.
module_enable($modules);
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
// The list of modules already have all the dependencies, but they might not
// be in the correct order. Still pass $enable_dependencies = TRUE so that
// Drupal will enable the modules in the correct order.
module_disable($modules);
}
/**
* 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);
// Because normally system_modules_submit would call this function if modules
// had been changed, in this case we are submitting the module form without
// any changes, so we need to clear caches manually.
drupal_flush_all_caches();
}
/**
* 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',
);
}

View File

@@ -0,0 +1,229 @@
<?php
/**
* @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.
*/
/**
* Return the list of modules required by drupal.
*
* We use that function name for onward compatibility with drupal 6 and 7.
*/
function drupal_required_modules() {
return array('block', 'filter', 'node', 'system', 'user', 'watchdog');
}
/**
* Returns drupal required modules, including their dependencies.
*/
function _drush_drupal_required_modules($module_info) {
return drupal_required_modules();
}
/**
* 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 = array_reverse($module_info[$module]->info['dependencies']);
$unmet_dependencies = array_diff($dependencies, array_keys($module_info));
if (!empty($unmet_dependencies)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
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'
);
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* @file
* Specific functions for a drupal 6 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;
}
/**
* Returns drupal required modules, including their dependencies.
*
* A module may alter other module's .info to set a dependency on it.
* See for example http://drupal.org/project/phpass
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($required as $module) {
$required = array_merge($required, $module_info[$module]->info['dependencies']);
}
return $required;
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$dependencies = array_reverse($module_info[$module]->info['dependencies']);
$unmet_dependencies = array_diff($dependencies, array_keys($module_info));
if (!empty($unmet_dependencies)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
$status[$key]['unmet-dependencies'] = $unmet_dependencies;
$status[$key]['dependencies'] = $dependencies;
}
return $status;
}
/**
* Return dependents of modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependents for each one of $modules
*/
function drush_module_dependents($modules, $module_info) {
$dependents = array();
foreach ($modules as $module) {
$dependents = array_merge($dependents, $module_info[$module]->info['dependents']);
}
return array_unique($dependents);
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// Try to install modules previous to enabling.
foreach ($modules as $module) {
_drupal_install_module($module);
}
module_enable($modules);
}
/**
* 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',
);
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Install Drupal 7+
*/
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
if (is_null($profile)) {
$profile = 'standard';
}
define('MAINTENANCE_MODE', 'install');
require_once DRUPAL_ROOT . '/includes/install.core.inc';
$db_spec = drush_sql_read_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' => array(
'driver' => $db_spec['driver'],
$db_spec['driver'] => $db_spec,
'op' => dt('Save and continue'),
),
'install_configure_form' => array(
'site_name' => drush_get_option('site-name', 'Site-Install'),
'site_mail' => drush_get_option('site-mail', 'admin@example.com'),
'account' => array(
'name' => 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),
'op' => dt('Save and continue'),
),
),
);
// Merge in the additional options.
foreach ($additional_form_options as $key => $value) {
$current = &$settings['forms'];
foreach (explode('.', $key) as $param) {
$current = &$current[$param];
}
$current = $value;
}
drush_log(dt('Starting Drupal installation. This takes a few seconds ...'), 'ok');
install_drupal($settings);
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* Install Drupal 6.x
*/
function drush_core_site_install_version($profile) {
if (is_null($profile)) {
$profile = 'default';
}
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
// We need to disable reporting of E_NOTICE if we want to read the command's output
// on Windows, because of how Windows is handling output order when using 2>&1
// redirect added to the command in drush_shell_exec(). We will actually take out
// all but fatal errors. See http://drupal.org/node/985716 for more information.
$phpcode = 'error_reporting(E_ERROR);' . _drush_site_install6_cookies($profile). ' include("'. $drupal_root .'/install.php");';
drush_shell_exec('php -r %s', $phpcode);
$cli_output = drush_shell_exec_output();
$cli_cookie = end($cli_output);
// We need to bootstrap the database to be able to check the progress of the
// install batch process since we're not duplicating the install process using
// drush_batch functions, but calling the process directly.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
$status = _drush_site_install6_stage($profile, $cli_cookie, "start");
if ($status === FALSE) {
return FALSE;
}
$status = _drush_site_install6_stage($profile, $cli_cookie, "do_nojs");
if ($status === FALSE) {
return FALSE;
}
$status = _drush_site_install6_stage($profile, $cli_cookie, "finished");
if ($status === FALSE) {
return FALSE;
}
$account_pass = drush_get_option('account-pass', 'admin');
$phpcode = _drush_site_install6_cookies($profile, $cli_cookie);
$phpcode .= '
$_POST = 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 .'"
)
),
"date_default_timezone"=>"0",
"clean_url"=>'. drush_get_option('clean-url', TRUE) .',
"form_id"=>"install_configure_form",
"update_status_module" => array("1"=>"1")
);
include("'. $drupal_root .'/install.php");';
drush_shell_exec('php -r %s', $phpcode);
}
/**
* Submit a given op to install.php; if a meta "Refresh" tag
* is returned in the result, then submit that op as well.
*/
function _drush_site_install6_stage($profile, $cli_cookie, $initial_op) {
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
// Remember the install task at the start of the stage
$install_task = _drush_site_install6_install_task();
$op = $initial_op;
while (!empty($op)) {
$phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="' . $op . '"; include("'. $drupal_root .'/install.php");';
drush_shell_exec('php -r %s', $phpcode);
$output = implode("\n", drush_shell_exec_output());
// Check for a "Refresh" back to the do_nojs op; e.g.:
// <meta http-equiv="Refresh" content="0; URL=http://default/install.php?locale=en&profile=wk_profile6&id=1&op=do_nojs">
// If this pattern is NOT found, then go on to the "finished" step.
$matches = array();
$match_result = preg_match('/http-equiv="Refresh".*op=([a-zA-Z0-9_]*)/', $output, $matches);
if ($match_result) {
$op = $matches[1];
}
else {
$op = '';
}
}
if (($install_task == _drush_site_install6_install_task()) && ($initial_op != "finished")) {
return drush_set_error('DRUSH_SITE_INSTALL_FAILED', dt("The site install task '!task' failed.", array('!task' => $install_task)));
}
return TRUE;
}
/**
* Utility function to grab/set current "cli cookie".
*/
function _drush_site_install6_cookies($profile, $cookie = NULL) {
$drupal_base_url = parse_url(drush_get_option('uri', 'http://default'));
$output = '$_GET=array("profile"=>"' . $profile . '", "locale"=>"' . drush_get_option('locale', 'en') . '", "id"=>"1"); $_REQUEST=&$_GET;';
$output .= 'define("DRUSH_SITE_INSTALL6", TRUE);$_SERVER["SERVER_SOFTWARE"] = NULL;';
$output .= '$_SERVER["SCRIPT_NAME"] = "/install.php";';
$output .= '$_SERVER["HTTP_HOST"] = "'.$drupal_base_url['host'].'";';
$output .= '$_SERVER["REMOTE_ADDR"] = "127.0.0.1";';
if ($cookie) {
$output .= sprintf('$_COOKIE=unserialize("%s");', str_replace('"', '\"', $cookie));
}
else {
$output .= 'function _cli_cookie_print(){print(serialize(array(session_name()=>session_id())));}
register_shutdown_function("_cli_cookie_print");';
}
return $output;
}
/**
* Utility function to check the install_task. We are
* not bootstrapped to a high enough level to use variable_get.
*/
function _drush_site_install6_install_task() {
if ($data = db_result(db_query("SELECT value FROM {variable} WHERE name = 'install_task'",1))) {
$result = unserialize($data);
}
return $result;
}

View File

@@ -0,0 +1,321 @@
<?php
/**
* @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');
/**
* Drupal's update.inc has functions that are in previous update_X.inc files
* for example, update_check_incompatibility() which can prove useful when
* enabling modules.
*/
require_once DRUPAL_ROOT . '/includes/update.inc';
/**
* 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/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();
include_once DRUPAL_ROOT . '/includes/batch.inc';
drupal_load_updates();
update_fix_compatibility();
// Change query-strings on css/js files to enforce reload for all users.
_drupal_flush_css_js();
// Flush the cache of all data for the update status module.
if (db_table_exists('cache_update')) {
cache_clear_all('*', 'cache_update', TRUE);
}
module_list(TRUE, FALSE, TRUE);
}
function update_main() {
update_main_prepare();
$pending = update_get_update_list();
$start = array();
// Ensure system module's updates run first
$start['system'] = array();
// Print a list of pending updates for this module and get confirmation.
if (sizeof($pending)) {
drush_print(dt('The following updates are pending:'));
drush_print();
foreach ($pending as $module => $updates) {
if (isset($updates['start'])) {
drush_print($module . ' module : ');
if (isset($updates['start'])) {
$start[$module] = $updates['start'];
foreach ($updates['pending'] as $update) {
drush_print($update, 2);
}
}
drush_print();
}
}
if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
return drush_user_abort();
}
drush_update_batch($start);
}
else {
drush_log(dt("No database updates required"), 'success');
}
}
function _update_batch_command($id) {
update_main_prepare();
drush_batch_command($id);
}
/**
* Start the database update batch process.
*
* @param $start
* An array of all the modules and which update to start at.
* @param $redirect
* Path to redirect to when the batch has finished processing.
* @param $url
* URL of the batch processing page (should only be used for separate
* scripts like update.php).
* @param $batch
* Optional parameters to pass into the batch API.
* @param $redirect_callback
* (optional) Specify a function to be called to redirect to the progressive
* processing page.
*/
function drush_update_batch($start) {
// Resolve any update dependencies to determine the actual updates that will
// be run and the order they will be run in.
$updates = update_resolve_dependencies($start);
// Store the dependencies for each update function in an array which the
// batch API can pass in to the batch operation each time it is called. (We
// do not store the entire update dependency array here because it is
// potentially very large.)
$dependency_map = array();
foreach ($updates as $function => $update) {
$dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
}
$operations = array();
foreach ($updates as $update) {
if ($update['allowed']) {
// Set the installed version of each module so updates will start at the
// correct place. (The updates are already sorted, so we can simply base
// this on the first one we come across in the above foreach loop.)
if (isset($start[$update['module']])) {
drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
unset($start[$update['module']]);
}
// Add this update function to the batch.
$function = $update['module'] . '_update_' . $update['number'];
$operations[] = array('drush_update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
}
}
$batch['operations'] = $operations;
$batch += array(
'title' => 'Updating',
'init_message' => 'Starting updates',
'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
'finished' => 'drush_update_finished',
'file' => 'includes/update.inc',
);
batch_set($batch);
drush_backend_batch_process('updatedb-batch-process');
}
function drush_update_finished($success, $results, $operations) {
// Nothing to do here. All caches already cleared. Kept as documentation of 'finished' callback.
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* @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?'))) {
return drush_user_abort();
}
$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');
}
}

View File

@@ -0,0 +1,504 @@
<?php
/**
* @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']);
// Create the semaphore table now -- the menu system after 6.15 depends on
// this table, and menu code runs in updates prior to the table being
// created in its original update function, system_update_6054().
$schema['semaphore'] = array(
'fields' => array(
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => ''),
'value' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => ''),
'expire' => array(
'type' => 'float',
'size' => 'big',
'not null' => TRUE),
),
'indexes' => array('expire' => array('expire')),
'primary key' => array('name'),
);
db_create_table($ret, 'semaphore', $schema['semaphore']);
}
return $ret;
}
/**
* Check update requirements and report any errors.
*/
function update_check_requirements() {
// Check the system module requirements only.
$requirements = module_invoke('system', 'requirements', 'update');
$severity = drupal_requirements_severity($requirements);
// If there are issues, report them.
if ($severity != REQUIREMENT_OK) {
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] != REQUIREMENT_OK) {
$message = isset($requirement['description']) ? $requirement['description'] : '';
if (isset($requirement['value']) && $requirement['value']) {
$message .= ' (Currently using '. $requirement['title'] .' '. $requirement['value'] .')';
}
drush_log($message, 'warning');
}
}
}
}
/**
* Create the batch table.
*
* This is part of the Drupal 5.x to 6.x migration.
*/
function update_create_batch_table() {
// If batch table exists, update is not necessary
if (db_table_exists('batch')) {
return;
}
$schema['batch'] = array(
'fields' => array(
'bid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
'token' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
'timestamp' => array('type' => 'int', 'not null' => TRUE),
'batch' => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
),
'primary key' => array('bid'),
'indexes' => array('token' => array('token')),
);
$ret = array();
db_create_table($ret, 'batch', $schema['batch']);
return $ret;
}
function update_main_prepare() {
global $profile;
// Some unavoidable errors happen because the database is not yet up-to-date.
// Our custom error handler is not yet installed, so we just suppress them.
drush_errors_off();
require_once './includes/bootstrap.inc';
// Minimum load of components.
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_extensions();
include_once './includes/batch.inc';
drupal_load_updates();
// Disable anything in the {system} table that is not compatible with the current version of Drupal core.
_drush_log_update_sql(update_fix_compatibility());
}
function update_main() {
update_main_prepare();
$start = array();
$has_updates = FALSE;
$modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
foreach ($modules as $module => $schema_version) {
$updates = drupal_get_schema_versions($module);
// Skip incompatible module updates completely, otherwise test schema versions.
if (!update_check_incompatibility($module) && $updates !== FALSE && $schema_version >= 0) {
// module_invoke returns NULL for nonexisting hooks, so if no updates
// are removed, it will == 0.
$last_removed = module_invoke($module, 'update_last_removed');
if ($schema_version < $last_removed) {
drush_set_error('PROVISION_DRUPAL_UPDATE_FAILED', dt( $module .' module can not be updated. Its schema version is '. $schema_version .'. Updates up to and including '. $last_removed .' have been removed in this release. In order to update '. $module .' module, you will first <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?'))) {
return drush_user_abort();
}
// Proceed with running all pending updates.
$operations = array();
foreach ($start as $module => $version) {
drupal_set_installed_schema_version($module, $version - 1);
$updates = drupal_get_schema_versions($module);
$max_version = max($updates);
if ($version <= $max_version) {
drush_log(dt('Updating module @module from schema version @start to schema version @max', array('@module' => $module, '@start' => $version - 1, '@max' => $max_version)));
foreach ($updates as $update) {
if ($update >= $version) {
$operations[] = array('_update_do_one', array($module, $update));
}
}
}
else {
drush_log(dt('No database updates for module @module', array('@module' => $module)), 'success');
}
}
$batch = array(
'operations' => $operations,
'title' => 'Updating',
'init_message' => 'Starting updates',
'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
'finished' => 'update_finished',
);
batch_set($batch);
$batch =& batch_get();
$batch['progressive'] = FALSE;
drush_backend_batch_process('updatedb-batch-process');
}
else {
drush_log(dt("No database updates required"), 'success');
}
}
/**
* A simplified version of the batch_do_one function from update.php
*
* This does not mess with sessions and the like, as it will be used
* from the command line
*/
function _update_do_one($module, $number, &$context) {
// If updates for this module have been aborted
// in a previous step, go no further.
if (!empty($context['results'][$module]['#abort'])) {
return;
}
$function = $module .'_update_'. $number;
drush_log("Executing $function", 'success');
if (function_exists($function)) {
$ret = $function($context['sandbox']);
$context['results'][$module] = $ret;
_drush_log_update_sql($ret);
}
if (isset($ret['#finished'])) {
$context['finished'] = $ret['#finished'];
unset($ret['#finished']);
}
if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) {
drupal_set_installed_schema_version($module, $number);
}
}
function _update_batch_command($id) {
update_main_prepare();
drush_batch_command($id);
}

View File

@@ -0,0 +1,303 @@
<?php
/**
* @file
* Field API's drush integration
*/
/**
* Implementation of hook_drush_help().
*/
function field_drush_help($section) {
switch ($section) {
case 'meta:field:title':
return dt('Field commands');
case 'meta:field:summary':
return dt('Manipulate Drupal 7+ fields.');
}
}
/**
* Implementation of hook_drush_command().
*/
function field_drush_command() {
$items['field-create'] = array(
'description' => 'Create fields and instances. Returns urls for field editing.',
'core' => array('7+'),
'drupal_dependencies' => array('field_ui'),
'arguments' => array(
'bundle' => 'Content type (for nodes). Name of bundle to attach fields to. Required.',
'field_spec' => 'Comma delimited triple in the form: field_name,field_type,widget_name. If widget_name is omitted, the default widget will be used. Separate multiple fields by space. If omitted, a wizard will prompt you.'
),
'options' => array(
'entity_type' => 'Type of entity (e.g. node, user, comment). Defaults to node.',
),
'examples' => array(
'drush field-create article' => 'Define new article fields via interactive prompts.',
'open `drush field-create article`' => 'Define new article fields and then open field edit form for refinement.',
'drush field-create article city,text,text_textfield subtitle,text,text_textfield' => 'Create two new fields.'
),
);
$items['field-update'] = array(
'description' => 'Return URL for field editing web page.',
'core' => array('7+'),
'drupal_dependencies' => array('field_ui'),
'arguments' => array(
'field_name' => 'Name of field that needs updating.',
),
'examples' => array(
'field-update comment_body' => 'Quickly navigate to a field edit web page.',
),
);
$items['field-delete'] = array(
'description' => 'Delete a field and its instances.',
'core' => array('7+'),
'arguments' => array(
'field_name' => 'Name of field to delete.',
),
'options' => array(
'bundle' => 'Only delete the instance attached to this bundle. If omitted, admin can choose to delete one instance or whole field.',
'entity_type' => 'Disambiguate a particular bundle from identically named bundles. Usually not needed.'
),
'examples' => array(
'field-delete city' => 'Delete the city field and any instances it might have.',
'field-delete city --bundle=article' => 'Delete the city instance on the article bundle',
),
);
$items['field-clone'] = array(
'description' => 'Clone a field and all its instances.',
'core' => array('7+'),
'arguments' => array(
'source_field_name' => 'Name of field that will be cloned',
'target_field_name' => 'Name of new, cloned field.',
),
'examples' => array(
'field-clone tags labels' => 'Copy \'tags\' field into a new field \'labels\' field which has same instances.',
'open `field-clone tags labels`' => 'Clone field and then open field edit forms for refinement.',
),
);
$items['field-info'] = array(
'description' => 'View information about fields, field_types, and widgets.',
'drupal_dependencies' => array('field_ui'),
'core' => array('7+'),
'arguments' => array(
'type' => 'Recognized values: fields, types. If omitted, a choice list appears.',
),
'options' => array(
'pipe' => 'Return field information table as CSV.',
),
'examples' => array(
'field-info types' => 'Show a table which lists all field types and their available widgets',
),
);
return $items;
}
function drush_field_create($bundle) {
$entity_type = drush_get_option('entity_type', 'node');
$args = func_get_args();
array_shift($args);
if (empty($args)) {
// Just one item in this array for now.
$args[] = drush_field_create_wizard();
}
// Iterate over each field spec.
foreach ($args as $string) {
list($name, $type, $widget) = explode(',', $string);
$info = field_info_field($name);
if (empty($info)) {
// Field does not exist already. Create it.
$field = array(
'field_name' => $name,
'type' => $type,
);
drush_op('field_create_field', $field);
}
// Create the instance.
$instance = array(
'field_name' => $name,
'entity_type' => $entity_type,
'bundle' => $bundle,
);
if ($widget) {
$instance['widget'] = array('type' => $widget);
}
drush_op('field_create_instance', $instance);
$urls[] = url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $name, array('absolute' => TRUE));
}
drush_print(implode(' ', $urls));
}
// Copy of function _field_ui_bundle_admin_path() since we don't want to load UI module.
function drush_field_ui_bundle_admin_path($entity_type, $bundle_name) {
$bundles = field_info_bundles($entity_type);
$bundle_info = $bundles[$bundle_name];
if (isset($bundle_info['admin'])) {
return isset($bundle_info['admin']['real path']) ? $bundle_info['admin']['real path'] : $bundle_info['admin']['path'];
}
}
function drush_field_update($field_name) {
$info = field_info_field($field_name);
foreach ($info['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$urls[] = url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $field_name, array('absolute' => TRUE));
}
}
drush_print(implode(' ', $urls));
}
function drush_field_delete($field_name) {
$info = field_info_field($field_name);
$confirm = TRUE;
if (!$bundle = drush_get_option('bundle')) {
foreach ($info['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$all_bundles[] = $bundle;
}
}
if (count($bundles) > 1) {
$options = array_merge(array('all' => dt('All bundles')), drupal_map_assoc($bundles));
$bundle = drush_choice($options, dt("Choose a particular bundle or 'All bundles'"));
$confirm = FALSE;
}
else {
if (!drush_confirm(dt('Do you want to delete the %field_name field?', array('%field_name' => $field_name)))) {
return drush_user_abort();
}
}
}
if ($bundle == 'all') {
foreach ($info['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$instance = field_info_instance($entity_type, $field_name, $bundle);
drush_op('field_delete_instance', $instance);
}
}
}
else {
$entity_type = drush_field_get_entity_from_bundle($bundle);
$instance = field_info_instance($entity_type, $field_name, $bundle);
drush_op('field_delete_instance', $instance);
}
// If there are no more bundles, delete the field.
$info = field_info_field($field_name);
if (empty($info['bundles'])) {
drush_op('field_delete_field', $field_name);
}
}
function drush_field_clone($source_field_name, $target_field_name) {
if (!$info = field_info_field($source_field_name)) {
return drush_set_error(dt('%source not found in field list.', array('%source' => $source_field_name)));
}
unset($info['id']);
$info['field_name'] = $target_field_name;
$target = drush_op('field_create_field', $info);
foreach ($info['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$instance = field_info_instance($entity_type, $source_field_name, $bundle);
$instance['field_name'] = $target_field_name;
unset($instance['id']);
$instance['field_id'] = $target['id'];
drush_op('field_create_instance', $instance);
$urls[] = url(drush_field_ui_bundle_admin_path($entity_type, $bundle) . '/fields/' . $target_field_name, array('absolute' => TRUE));
}
}
drush_print(implode(' ', $urls));
}
function drush_field_info($type = NULL) {
if (is_null($type)) {
$type = drush_choice(drupal_map_assoc(array('types', 'fields')), dt('Which information do you wish to see?'));
}
switch ($type) {
case 'fields':
$rows[] = array(
dt('Field name'),
dt('Field type'),
dt('Bundles'),
);
$info = field_info_fields();
foreach ($info as $field_name => $field) {
$bundle_strs = array();
foreach ($field['bundles'] as $entity_type => $bundles) {
$bundle_strs[] = implode(',', $bundles);
}
$row = array(
$field_name,
$field['type'],
implode(' ', $bundle_strs),
);
$rows[] = $row;
$pipe[] = implode(',', $row);
}
break;
case 'types':
$rows[] = array(
dt('Field type'),
dt('Default widget'),
dt('Widgets'),
);
$info = field_info_field_types();
module_load_include('inc', 'field_ui', 'field_ui.admin');
$widgets = field_info_widget_types();
foreach ($info as $type_name => $type) {
$widgets = field_ui_widget_type_options($type_name);
$row = array(
$type_name,
$type['default_widget'],
implode(', ', array_keys($widgets)),
);
$rows[] = $row;
$pipe[] = implode(',', $row);
}
break;
}
drush_print_table($rows, TRUE);
drush_print_pipe($pipe);
return $rows;
}
/**
* Prompt user enough to create basic field and instance.
*
* @return array $field_spec
* An array of brief field specifications.
*/
function drush_field_create_wizard() {
$specs[] = drush_prompt(dt('Field name'));
module_load_include('inc', 'field_ui', 'field_ui.admin');
$types = field_ui_field_type_options();
$field_type = drush_choice($types, dt('Choose a field type'));
$specs[] = $field_type;
$widgets = field_ui_widget_type_options($field_type);
$specs[] = drush_choice($widgets, dt('Choose a widget'));
return implode(',', $specs);
}
function drush_field_get_entity_from_bundle($bundle) {
if (drush_get_option('entity_type')) {
return drush_get_option('entity_type');
}
else {
$info = field_info_bundles();
foreach ($info as $entity_type => $bundles) {
if (isset($bundles[$bundle])) {
return $entity_type;
}
}
}
}

View File

@@ -0,0 +1,261 @@
<?php
/**
* Build a fake command for the purposes of showing examples and options.
*/
function drush_global_options_command($brief = FALSE) {
$global_options_help = array(
'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help. Run `drush topic` to read even more documentation.',
'sections' => array(
'options' => 'Global options (see `drush topic core-global-options` for the full list).',
),
'options' => drush_get_global_options($brief),
'examples' => array(
'drush dl cck zen' => 'Download CCK module and Zen theme.',
'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.',
),
);
$global_options_help += drush_command_defaults('global-options', 'global_options', __FILE__);
return $global_options_help;
}
/**
* Command callback for help command. This is the default command, when none
* other has been specified.
*/
function drush_core_help() {
$commands = func_get_args();
if (empty($commands)) {
// For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION.
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
$implemented = drush_get_commands();
// Organize all commands into categories
$command_categories = array();
$category_map = array();
foreach ($implemented as $key => $candidate) {
if ((!array_key_exists('is_alias', $candidate) || !$candidate['is_alias']) && !$candidate['hidden']) {
$category = $candidate['commandfile'];
// If we have decided to remap a category, remap every command
if (array_key_exists($category, $category_map)) {
$category = $category_map[$category];
}
if (!array_key_exists($category, $command_categories)) {
$title = drush_command_invoke_all('drush_help', "meta:$category:title");
$alternate_title = '';
if (!$title) {
// If there is no title, then check to see if the
// command file is stored in a folder with the same
// name as some other command file (e.g. 'core') that
// defines a title.
$alternate = basename($candidate['path']);
$alternate_title = drush_command_invoke_all('drush_help', "meta:$alternate:title");
}
if (!empty($alternate_title)) {
$category_map[$category] = $alternate;
$category = $alternate;
$title = $alternate_title;
}
$command_categories[$category]['title'] = empty($title) ? '' : $title[0];
$summary = drush_command_invoke_all('drush_help', "meta:$category:summary");
if ($summary) {
$command_categories[$category]['summary'] = $summary[0];
}
}
$command_categories[$category]['commands'][] = $key;
}
}
// Sort the command categories; make sure that 'core' is
// always first in the list
$core_category = array('core' => $command_categories['core']);
unset($command_categories['core']);
// Post-process the categories that have no title.
// Any that have fewer than 4 commands will go into a
// section called "other".
$processed_categories = array();
$misc_categories = array();
$other_commands = array();
$other_categories = array();
foreach ($command_categories as $key => $info) {
if (empty($info['title'])) {
$one_category = $key;
if (count($info['commands']) < 4) {
$other_commands = array_merge($other_commands, $info['commands']);
$other_categories[] = $one_category;
}
else {
$info['title'] = dt("All commands in !category", array('!category' => $key));
$misc_categories[$one_category] = $info;
}
}
else {
$processed_categories[$key] = $info;
}
}
$other_category = array();
if (!empty($other_categories)) {
$other_category[implode(',', $other_categories)] = array('title' => dt("Other commands"), 'commands' => $other_commands);
}
asort($processed_categories);
asort($misc_categories);
$command_categories = array_merge($core_category, $processed_categories, $misc_categories, $other_category);
// If the user specified --filter w/out a value, then
// present a list of help options.
if (drush_get_option('filter', FALSE) === TRUE) {
$help_categories = array();
foreach ($command_categories as $key => $info) {
$description = $info['title'];
if (array_key_exists('summary', $info)) {
$description .= ": " . $info['summary'];
}
$help_categories[$key] = $description;
}
$result = drush_choice($help_categories, 'Select a help category:');
if (!$result) {
return drush_user_abort();
}
drush_set_option('filter', $result);
}
if (drush_get_option('html')) {
drush_print(drush_help_html_header());
}
// Make a fake command section to hold the global options, then print it.
$global_options_help = drush_global_options_command(TRUE);
if ((!drush_get_option('html')) && (!drush_get_option('filter'))) {
drush_print_help($global_options_help);
}
// Filter out categories that the user does not want to see
if ($filter_category = drush_get_option('filter')) {
if (!array_key_exists($filter_category, $command_categories)) {
return drush_set_error('DRUSH_NO_CATEGORY', dt("The specified command category !filter does not exist.", array('!filter' => $filter_category)));
}
$command_categories = array($filter_category => $command_categories[$filter_category]);
}
// If the user specified --sort, then merge all of the remaining
// categories together
if (drush_get_option('sort', FALSE)) {
$combined_commands = array();
foreach ($command_categories as $key => $info) {
$combined_commands = array_merge($combined_commands, $info['commands']);
}
$command_categories = array('all' => array('commands' => $combined_commands, 'title' => dt("Commands:")));
}
// Next, print out the table of commands by category.
$all_commands = array();
foreach ($command_categories as $key => $info) {
// Get the commands in this category and sort them
$commands = $info['commands'];
sort($commands);
// Remove hidden commands and build output for drush_print_table.
$rows = array();
foreach($commands as $cmd) {
$command = $implemented[$cmd];
$all_commands[$cmd] = $command;
$name = $command['aliases'] ? $cmd . ' (' . implode(', ', $command['aliases']) . ')': $cmd;
$rows[$cmd] = array($name, $command['description']);
$pipe[] = $cmd;
}
// Vary the output by mode: text or html
if (drush_get_option('html')) {
$sorted_commands = array();
foreach($commands as $cmd) {
$sorted_commands[$cmd] = $implemented[$cmd];
}
drush_print("<h3>" . $info['title'] . "</h3>");
drush_print(drush_help_html_command_list($sorted_commands));
}
else {
drush_print($info['title'] . ": (" . $key . ")");
drush_print_table($rows, FALSE, array(0 => 20));
}
}
// Print out the long-form help for all commands
if (drush_get_option('html')) {
drush_print(drush_help_html_global_options($global_options_help));
drush_print(drush_help_html($all_commands));
}
// Newline-delimited list for use by other scripts. Set the --pipe option.
if (drush_get_option('pipe') && isset($pipe)) {
sort($pipe);
drush_print_pipe(implode(" ", $pipe));
}
return;
}
else {
$result = TRUE;
while ((count($commands) > 0) && !drush_get_error()) {
$result = drush_show_help(array_shift($commands));
}
return $result;
}
return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => implode(" ", $commands))));
}
/**
* Return an HTML page header that includes all global options.
*/
function drush_help_html_header() {
$output = "<html><head><title>drush help</title><style>dt {font-size: 110%; font-weight: bold}</style></head><body>\n";
}
function drush_help_html_global_options($global_options_help) {
// Global options
$global_option_rows = drush_format_help_section($global_options_help, 'options');
$output = '<h3>Global Options (see `drush topic core-global-options` for the full list)</h3><table>';
foreach ($global_option_rows as $row) {
$output .= "<tr>";
foreach ($row as $value) {
$output .= "<td>" . htmlspecialchars($value) . "</td>\n";
}
$output .= "</tr>";
}
$output .= "</table>\n";
return $output;
}
function drush_help_html_command_list($commands) {
// Command table
$output = "<table>\n";
foreach ($commands as $key => $command) {
$output .= " <tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n";
}
$output .= "</table>\n";
return $output;
}
/**
* Return an HTML page documenting all available commands and global options.
*/
function drush_help_html($commands) {
// 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($key);
$output .= ob_get_clean();
$output .= "</pre></dd>\n";
}
$output .= "</body></html>\n";
return $output;
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* @file
* Image module's drush integration.
*
* @todo image-build($field_name, $bundle, $style_name)
*/
/**
* Implementation of hook_drush_command().
*/
function image_drush_command() {
$items['image-flush'] = array(
'description' => 'Flush all derived images for a given style.',
'core' => array('7+'),
'drupal_dependencies' => array('image'),
'arguments' => array(
'style' => 'An image style machine name. If not provided, user may choose from a list of names.',
),
'options' => array(
'all' => 'Flush all derived images',
),
'examples' => array(
'drush image-flush' => 'Pick an image style and then delete its images.',
'drush image-flush thumbnail' => 'Delete all thumbnail images.',
'drush image-flush --all' => 'Flush all derived images. They will be regenerated on the fly.',
),
);
return $items;
}
function drush_image_flush($style_name = NULL) {
if (drush_get_option('all')) {
drush_image_flush_all();
}
elseif (empty($style_name)) {
$choices = drupal_map_assoc(array_keys(image_styles()));
$choices = array_merge(array('all' => 'all'), $choices);
$style_name = drush_choice($choices, dt("Choose a style to flush."));
if ($style_name == 'all') {
drush_image_flush_all();
}
else {
$commands = drush_get_commands();
return drush_dispatch($commands['image-flush'], array($style_name));
}
}
else {
if ($style = image_style_load($style_name)) {
image_style_flush($style);
drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), 'success');
}
else {
return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name)));
}
}
}
function drush_image_flush_all() {
foreach (image_styles() as $style) {
image_style_flush($style);
}
drush_log(dt('All image styles flushed'), 'success');
}

View File

@@ -0,0 +1,272 @@
<?php
/**
* 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 destination, then you need to know how
// rsync paths work, and put on the trailing '/' if you want it.
if ($source_settings['user-path'] == $destination_settings['user-path']) {
$source_path .= '/';
}
// Prompt for confirmation. This is destructive.
if (!drush_get_context('DRUSH_SIMULATE')) {
drush_print(dt("You will destroy data from !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path)));
if (!drush_confirm(dt('Do you really want to continue?'))) {
// was: return drush_set_error('CORE_SYNC_ABORT', 'Aborting.');
return drush_user_abort();
}
}
// Exclude settings is the default only when both the source and
// the destination are aliases or site names. Therefore, include
// settings will be the default whenever either the source or the
// destination contains a : or a /.
$include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE);
// Go ahead and call rsync with the paths we determined
return 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) {
// Exclude vcs reserved files.
$options = '';
if (!_drush_rsync_option_exists('include-vcs', $additional_options)) {
$vcs_files = drush_version_control_reserved_files();
foreach ($vcs_files as $file) {
$options .= ' --exclude="'.$file.'"';
}
}
$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(PATH_SEPARATOR, drush_get_option(array($include_exclude . '-path', $include_exclude . '-paths'), ''));
foreach ($inc_ex_path as $one_path_to_inc_ex) {
if (!empty($one_path_to_inc_ex)) {
$options .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"';
}
}
}
// 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',
'exclude-from',
'include',
'include-from',
'files-from',
'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',
);
// Check if the user has set $options['rsync-version'] to enable rsync legacy version support.
// Drush was written for rsync 2.6.9 or later, so assume that version if nothing was explicitly set.
$rsync_version = drush_get_option(array('rsync-version','source-rsync-version','target-rsync-version'), '2.6.9');
foreach ($rsync_available_options as $test_option) {
$value = drush_get_option_override($additional_options, $test_option);
// Downgrade some options for older versions of rsync
if ($test_option == 'remove-source-files') {
if (version_compare($rsync_version, '2.6.4', '<')) {
$test_option = NULL;
drush_log('Rsync does not support --remove-sent-files prior to version 2.6.4; some temporary files may remain undeleted.', 'warning');
}
elseif (version_compare($rsync_version, '2.6.9', '<')) {
$test_option = 'remove-sent-files';
}
}
if ((isset($test_option)) && (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);
$result = ($exec_result == 0);
}
else {
$result = drush_shell_exec($exec);
}
if (!$result) {
drush_set_error('DRUSH_RSYNC_FAILED', dt("Could not rsync from !source to !dest", array('!source' => $source, '!dest' => $destination)));
}
return $result;
}
function _drush_rsync_option_exists($option, $additional_options) {
if (array_key_exists($option, $additional_options)) {
return TRUE;
}
else {
return drush_get_option($option, FALSE);
}
}

View File

@@ -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 you 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']);

View File

@@ -0,0 +1,88 @@
<?php
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_user_abort();
}
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_user_abort();
}
if (drush_drupal_major_version() >= 7) {
drush_op('search_reindex');
}
else {
drush_op('search_wipe');
}
if (drush_get_option('immediate')) {
drush_op('_drush_core_search_index');
drush_log(dt('The search index has been rebuilt.'), 'ok');
}
else {
drush_log(dt('The search index will be rebuilt.'), 'ok');
}
}

View File

@@ -0,0 +1,133 @@
<?php
/**
* Command validate.
*/
function drush_core_site_install_validate() {
if ($sites_subdir = drush_get_option('sites-subdir')) {
$lower = strtolower($sites_subdir);
if ($sites_subdir != $lower) {
drush_log(dt('Only lowercase sites-subdir are valid. Switching to !lower.', array('!lower' => $lower)), 'warning');
drush_set_option('sites-subdir', $lower);
}
}
}
/**
* Perform setup tasks for installation.
*/
function drush_core_pre_site_install() {
if (!$db_spec = drush_sql_read_db_spec()) {
drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.'));
return;
}
if ($sites_subdir = drush_get_option('sites-subdir')) {
// Needed so that we later bootstrap into the right site.
drush_set_option('uri', 'http://'.$sites_subdir);
}
else {
$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_user_abort();
}
// 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;
}
elseif (drush_drupal_major_version() == 6) {
// On D6, we have to write $db_url ourselves. In D7+, the installer does it.
file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_get_option('db-url') . "';\n", FILE_APPEND);
// Instead of parsing and performing string replacement on the configuration file,
// the options are appended and override the defaults.
// Database table prefix
if (!empty($db_spec['db_prefix'])) {
if (is_array($db_spec['db_prefix'])) {
// Write db_prefix configuration as an array
$db_prefix_config = '$db_prefix = ' . var_export($db_spec['db_prefix'], TRUE) . ';';
}
else {
// Write db_prefix configuration as a string
$db_prefix_config = '$db_prefix = \'' . $db_spec['db_prefix'] . '\';';
}
file_put_contents($settingsfile, "\n" . $db_prefix_config . "\n", FILE_APPEND);
}
}
}
// 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.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
// Drop and create DB if needed.
$db_name = $db_spec['database'];
$scheme = _drush_sql_get_scheme($db_spec);
$simulate = drush_get_context('DRUSH_SIMULATE');
if ($scheme === 'sqlite') {
// With SQLite, we don't DROP DATABASEs. Each database is in a single file,
// so we just remove the file. We also don't CREATE DATABASEs; it is created
// when SQLite attempts to open a database file which doesn't exist.
if (file_exists($db_spec['database']) && !$simulate) {
if (!unlink($db_spec['database'])) {
drush_set_error(dt('Could not drop database: @name', array('@name' => $db_name)));
}
}
}
else {
drush_sql_empty_db($db_spec);
}
return TRUE;
}
/**
* Command callback.
*/
function drush_core_site_install($profile = NULL) {
$args = func_get_args();
$form_options = array();
if ($args) {
// The first argument is the profile.
$profile = array_shift($args);
// Subsequent arguments are additional form values.
foreach ($args as $arg) {
list($key, $value) = explode('=', $arg);
$form_options[$key] = $value;
}
}
drush_include_engine('drupal', 'site_install', drush_drupal_major_version());
drush_core_site_install_version($profile, $form_options);
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* @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.',
'component' => 'Print only the specified element from the full alias record.',
'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\'.',
),
'topics' => array('docs-aliases'),
);
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();
// Filter out the hidden items
foreach ($site_list as $site_name => $one_site) {
if (array_key_exists('#hidden', $one_site)) {
unset($site_list[$site_name]);
}
}
}
return $site_list;
}
/**
* Print out the specified site aliases using the format
* specified.
*/
function drush_sitealias_print() {
// Call bootstrap max, unless the caller requested short output
if (!drush_get_option('short', FALSE)) {
drush_bootstrap_max();
}
$site_list = _drush_sitealias_user_specified_list();
$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)) {
$component = drush_get_option('component');
if ($component) {
if (array_key_exists($component, $alias_record)) {
drush_print($alias_record[$component]);
}
else {
drush_set_error('DRUSH_NO_SUCH_ELEMENT', dt('The element @component was not found in the alias record for @site.', array('@component' => $component, '@site' => $site)));
}
}
else {
_drush_sitealias_print_record($alias_record, $site);
}
}
else {
drush_print($site);
}
}
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']);
unset($alias_record['#hidden']);
// We only want to output the 'root' item; don't output the '%root' path alias
if (array_key_exists('path-aliases', $alias_record) && array_key_exists('%root', $alias_record['path-aliases'])) {
unset($alias_record['path-aliases']['%root']);
// If there is nothing left in path-aliases, then clear it out
if (count($alias_record['path-aliases']) == 0) {
unset($alias_record['path-aliases']);
}
}
// Alias names contain an '@' when referenced, but do
// not contain an '@' when defined.
if (substr($alias_name,0,1) == '@') {
$alias_name = substr($alias_name,1);
}
if (!drush_get_option('show-passwords', FALSE)) {
drush_unset_recursive($alias_record, 'password');
}
$exported_alias = var_export($alias_record, TRUE);
drush_print('$aliases[\'' . $alias_name . '\'] = ' . $exported_alias . ';');
}
/**
* Use heuristics to attempt to convert from a site directory to a URI.
* This function should only be used when the URI really is unknown, as
* the mapping is not perfect.
*
* @param site_dir
* A directory, such as domain.com.8080.drupal
*
* @return string
* A uri, such as http://domain.com:8080/drupal
*/
function _drush_sitealias_site_dir_to_uri($site_dir) {
// Protect IP addresses NN.NN.NN.NN by converting them
// temporarily to NN_NN_NN_NN for now.
$uri = preg_replace("/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/", "$1_$2_$3_$4", $site_dir);
// Convert .[0-9]+. into :[0-9]+/
$uri = preg_replace("/\.([0-9]+)\./", ":$1/", $uri);
// Convert .[0-9]$ into :[0-9]
$uri = preg_replace("/\.([0-9]+)$/", ":$1", $uri);
// Convert .(com|net|org|info). into .(com|net|org|info)/
$uri = str_replace(array('.com.', '.net.', '.org.', '.info.'), array('.com/', '.net/', '.org/', '.info/'), $uri);
// If there is a / then convert every . after the / to /
// Then again, if we did this we would break if the path contained a "."
// I hope that the path would never contain a "."...
$pos = strpos($uri, '/');
if ($pos !== false) {
$uri = substr($uri, 0, $pos + 1) . str_replace('.', '/', substr($uri, $pos + 1));
}
// n.b. this heuristic works all the time if there is a port,
// it also works all the time if there is a port and no path,
// but it does not work for domains such as .co.jp with no path,
// and it can fail horribly if someone makes a domain like "info.org".
// Still, I think this is the best we can do short of consulting DNS.
// Convert from NN_NN_NN_NN back to NN.NN.NN.NN
$uri = preg_replace("/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)/", "$1.$2.$3.$4", $site_dir);
return 'http://' . $uri;
}

View File

@@ -0,0 +1,268 @@
<?php
/**
* @file
* Simpletest module drush integration.
*/
/**
* Implementation of hook_drush_command().
*/
function test_drush_command() {
$items = array();
$items['test-run'] = array(
'description' => "Run tests. Note that you must use the --uri option.",
'arguments' => array(
'targets' => 'A test class, a test group. If omitted, a list of test classes and test groups is presented. Delimit multiple targets using commas.',
),
'examples' => array(
'test-run' => 'List all available classes and groups.',
'sudo -u apache test-run --all' => 'Run all available tests. Avoid permission related failures by running as web server user.',
'test-run XMLRPCBasicTestCase' => 'Run one test class.',
'test-run XML-RPC' => 'Run all classes in a XML-RPC group.',
'test-run XML-RPC,Filter' => 'Run all tests from multiple groups/classes.',
'test-run XMLRPCBasicTestCase --methods="testListMethods, testInvalidMessageParsing"' => 'Run particular methods in the specified class or group.',
),
'options' => array(
'all' => 'Run all available tests',
'methods' => 'A comma delimited list of methods that should be run within the test class. Defaults to all methods.',
'dirty' => 'Skip cleanup of temporary tables and files. Helpful for reading debug() messages and other post-mortem forensics.',
'xml' => 'Output verbose test results to a specified directory using the JUnit test reporting format. Useful for integrating with Jenkins.'
),
'drupal dependencies' => array('simpletest'),
// If you DRUSH_BOOTSTRAP_DRUPAL_LOGIN, you fall victim to http://drupal.org/node/974768. We'd like
// to not bootstrap at all but simpletest uses Drupal to discover test classes,
// cache the lists of tests, file_prepare_directory(), variable lookup like
// httpauth creds, copy pre-built registry table from testing side, etc.
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
);
$items['test-clean'] = array(
'description' => "Clean temporary tables and files.",
'drupal dependencies' => array('simpletest'),
);
return $items;
}
// Command callback
function drush_test_clean() {
return simpletest_clean_environment();
}
// Validate hook
function drush_test_run_validate($specs = NULL) {
if (!drush_get_option('uri')) {
// No longer needed?
// return drush_set_error(dt("You must specify this site's URL using the --uri parameter."));
}
}
/**
* Test-run command callback.
*
* @specs
* A comman delimited string of test classes or group names.
*/
function drush_test_run($specs = NULL) {
cache_clear_all('simpletest', 'cache');
// Retrieve all tests and groups.
list($groups, $all_tests) = drush_test_get_all_tests();
if (drush_get_option('all')) {
// Run all tests.
foreach (array_keys($groups) as $group) {
foreach (array_keys($groups[$group]) as $class) {
drush_backend_invoke_args('test-run', array($class), array('--no-all'));
}
}
return;
}
elseif (empty($specs)) {
return drush_test_list($groups);
}
foreach (explode(',', $specs) as $spec) {
$spec = trim($spec);
// Specific test class specified.
if (in_array($spec, $all_tests)) {
simpletest_drush_run_test($spec);
if (!drush_get_option('dirty')) {
simpletest_clean_environment();
}
return;
}
// Specific group specified.
else if (isset($groups[$spec])) {
foreach (array_keys($groups[$spec]) as $class) {
drush_do_command_redispatch('test-run', array($class));
}
return;
}
}
}
/**
* Run a single test and display any failure messages.
*/
function simpletest_drush_run_test($class) {
if (drush_drupal_major_version() >= 7) {
$test_id = db_insert('simpletest_test_id')
->useDefaults(array('test_id'))
->execute();
}
else {
db_query('INSERT INTO {simpletest_test_id} (test_id) VALUES (default)');
$test_id = db_last_insert_id('simpletest_test_id', 'test_id');
}
$test = new $class($test_id);
if ($methods_string = drush_get_option('methods')) {
foreach (explode(',', $methods_string) as $method) {
$methods[] = trim($method);
}
$test->run($methods);
}
else {
$test->run();
}
$info = $test->getInfo();
$status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0)
|| (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'error' : 'ok');
drush_log($info['name'] . ' ' . _simpletest_format_summary_line($test->results), $status);
if ($dir = drush_get_option('xml')) {
drush_test_xml_results($test_id, $dir);
}
// If there were some failed tests show them.
if ($status === 'error') {
if (drush_drupal_major_version() >= 7) {
$args = array(':test_id' => $test_id);
$result = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $args);
foreach($result as $record) {
drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message", array('!function' => $record->function, '!message' => $record->message)));
}
}
else {
$result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $test_id);
while ($row = db_fetch_object($result)) {
drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message", array('!function' => $row->function, '!message' => $row->message)));
}
}
}
}
/**
* Retrieve all test groups and sanitize their names to make them command-line
* friendly.
*/
function simpletest_drush_test_groups($tests) {
$groups = array();
foreach (simpletest_categorize_tests($tests) as $name => $group) {
$sanitized = strtr($name, array(' ' => ''));
$groups[$sanitized] = $group;
}
return $groups;
}
// Print a listing of all available tests
function drush_test_list($groups) {
$rows[] = array(dt('Command'), dt('Description'));
$rows[] = array('-------', '-----------');
foreach ($groups as $group_name => $group_tests) {
foreach ($group_tests as $test_class => $test_info) {
if (!isset($rows[$test_info['group']])) {
$rows[$test_info['group']] = array($group_name, $test_info['group']);
}
$rows[] = array(" {$test_class}", " {$test_info['name']}");
}
}
return drush_print_table($rows, TRUE);
}
function drush_test_get_all_tests() {
if (function_exists('simpletest_get_all_tests')) {
$all_tests = simpletest_get_all_tests();
$groups = simpletest_drush_test_groups($all_tests);
}
else {
$groups = simpletest_test_get_all();
$all_tests = array();
foreach ($groups as $group) {
$all_tests = array_merge($all_tests, array_keys($group));
}
}
return array($groups, $all_tests);
}
/*
* Display test results.
*/
function drush_test_xml_results($test_id, $dir) {
$dir = is_string($dir) ? $dir : '.';
$results_map = array(
'pass' => 'Pass',
'fail' => 'Fail',
'exception' => 'Exception',
);
if (drush_drupal_major_version() >= 7) {
$results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
}
else {
$result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id);
$results = array();
while ($row = db_fetch_object($result)) {
$results[] = $row;
}
}
$test_class = '';
$xml_files = array();
foreach ($results as $result) {
if (isset($results_map[$result->status])) {
if ($result->test_class != $test_class) {
// Display test class every time results are for new test class.
if (isset($xml_files[$test_class])) {
file_put_contents($dir . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
unset($xml_files[$test_class]);
}
$test_class = $result->test_class;
if (!isset($xml_files[$test_class])) {
$doc = new DomDocument('1.0');
$root = $doc->createElement('testsuite');
$root = $doc->appendChild($root);
$xml_files[$test_class] = array('doc' => $doc, 'suite' => $root);
}
}
// Save the result into the XML:
$case = $xml_files[$test_class]['doc']->createElement('testcase');
$case->setAttribute('classname', $test_class);
list($class, $name) = explode('->', $result->function, 2);
$case->setAttribute('name', $name);
if ($result->status == 'fail') {
$fail = $xml_files[$test_class]['doc']->createElement('failure');
$fail->setAttribute('type', 'failure');
$fail->setAttribute('message', $result->message_group);
$text = $xml_files[$test_class]['doc']->createTextNode($result->message);
$fail->appendChild($text);
$case->appendChild($fail);
}
$xml_files[$test_class]['suite']->appendChild($case);
}
}
// Save the last one:
if (isset($xml_files[$test_class])) {
file_put_contents($dir . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
unset($xml_files[$test_class]);
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* @file
* Topic command and associated hooks.
*/
/**
* Implementation of hook_drush_command().
*
* @return
* An associative array describing your command(s).
*/
function topic_drush_command() {
$items['core-topic'] = array(
'description' => 'Read detailed documentation on a given topic.',
'arguments' => array(
'topic name' => 'The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).',
),
'examples' => array(
'drush topic' => 'Show all available topics.',
'drush topic docs-context' => 'Show documentation for the drush context API',
'drush docs-context' => 'Show documentation for the drush context API',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'aliases' => array('topic'),
'topics' => array('docs-readme'),
);
return $items;
}
/**
* Implement hook_drush_help_alter(). Show 'Topics' section on help detail.
*/
function topic_drush_help_alter($command) {
$implemented = drush_get_commands();
foreach ($command['topics'] as $topic_name) {
// We have a related topic. Inject into the $command so the topic displays.
$command['sections']['topic_section'] = 'Topics';
$command['topic_section'][$topic_name] = $implemented[$topic_name]['description'];
}
}
/**
* A command callback.
*
* Show a choice list of available topics and then dispatch to the respective command.
*
* @param string $topic_name
* A command name.
*/
function drush_topic_core_topic($topic_name = NULL) {
$commands = drush_get_commands();
if (is_null($topic_name)) {
// Show choice list.
foreach (drush_get_topics() as $key => $topic) {
$choices[$key] = $topic['description'];
}
natcasesort($choices);
if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)')) {
return;
}
}
// If the topic name is not found, check for
// "docs-$topic_name". This allows users to be
// just a bit lazy when selecting core topics by name.
if (!isset($commands[$topic_name]) && isset($commands["docs-$topic_name"])) {
$topic_name = "docs-$topic_name";
}
return drush_dispatch($commands[$topic_name]);
}
/**
* Retrieve all defined topics
*/
function drush_get_topics() {
$commands = drush_get_commands();
foreach ($commands as $key => $command) {
if (!empty($command['topic']) && empty($command['is_alias'])) {
$topics[$key] = $command;
}
}
return $topics;
}

View File

@@ -0,0 +1,703 @@
<?php
/**
* @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 (e.g. Drupal 6 to Drupal 7). A copy of the site is made, and then upgraded; the source site is not changed.',
'drupal dependencies' => array('update'),
'drush dependencies' => array('sql', 'pm', 'core'),
'core' => array(6), // Add 7 once drush supports 7 -> 8 upgrades.
'arguments' => array(
'target' => "The name of a sitealias, which points to the destination site. 'root' and 'uri' keys are required; db-url is recommended. See examples/aliases.drushrc.php for more information about creating a site alias.",
),
'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.',
'no-cache' => 'Transfer a fresh database from source site. Otherwise, DB dump is re-used for 24 hours.',
'core-only' => 'Stop after upgrading Drupal core; do not download and enable new versions of the site\'s modules.',
'force-sites-default' => 'Forces settings.php to be written in sites/default folder, even if source settings.php is not.',
'replace' => 'Replace target if it already exists. Default is to prompt.',
'reuse' => 'Reuse target if it already exists. Default is to prompt.',
'uninstall' => 'Comma-separated list of modules to uninstall in the target database prior to upgrade. n.b. The source site is not affected.',
),
'aliases' => array('sup'),
'topics' => array('docs-aliases'),
);
$items['site-upgrade-prepare'] = array(
'description' => 'Prior to running updatedb on Drupal core, disable all contrib modules and uninstall any module the user specified should be uninstalled. Called automatically by site-upgrade.',
'hidden' => TRUE,
'arguments' => array(
'modules' => 'The modules to disable.',
),
'options' => array(
'uninstall' => 'Comma-separated list of modules to uninstall prior to upgrade.',
),
);
$items['site-upgrade-modules'] = array(
'description' => 'Download, enable, and run updatedb on all contrib modules after an upgrade. Called automatically by site-upgrade.',
'hidden' => TRUE,
'arguments' => array(
'modules' => 'The modules to download and enable.',
),
'options' => array(
'projects' => 'The projects that must be downloaded to provide the specified module list.',
),
);
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. 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. Run this command from within your source site (D6). Note that this command uses pm-download and sql-sync internally so most options for those commands are valid here too.");
}
}
/**
* Do some sanity checks to make sure that we are ready to perform an upgrade, and
* that the command is being called with reasonable-looking parameters.
*/
function drush_upgrade_site_upgrade_validate($target_key = NULL) {
if (empty($target_key)) {
return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Missing argument: target'));
}
if (!$target_alias = drush_sitealias_get_record($target_key)) {
return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Site alias not found: @target-key. See example.drushrc.php.', array('@target-key' => $target_key)));
}
if (!file_exists(dirname($target_alias['root']))) {
drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Parent directory for site alias root not found: @root; this folder must exist before running site-upgrade. See example.drushrc.php.', array('@root' => dirname($target_alias['root']))));
}
if (realpath($target_alias['root']) == realpath(DRUPAL_ROOT)) {
drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt('Target site alias must have a different Drupal root directory than the source site. Both are at @root.', array('@root' => $target_alias['root'])));
}
}
/**
* Main command hook for site-upgrade.
*
* This runs bootstrapped to the SOURCE site.
*/
function drush_upgrade_site_upgrade($target_key) {
// Presume we are ready to go (n.b. some checks already performed in 'validate')
$ready_to_upgrade = TRUE;
$result = TRUE;
// PREPARE: Find the target version and determine the contrib projects and enabled modules installed.
$source_version = drush_drupal_major_version();
$target_version = $source_version + 1;
$target_alias = drush_sitealias_get_record($target_key);
if (empty($target_alias)) {
return drush_set_error('DRUSH_UPGRADE_NO_TARGET', dt("Could not find target site for upgrade: !target", array("!target" => $target_key)));
}
$destination_core = $target_alias['root'];
$destination_conf_path = conf_path();
if (drush_get_option('force-sites-default')) {
$destination_conf_path = 'sites/default';
}
// Get a list of enabled contrib extensions.
$values = drush_invoke_process_args('pm-list', array(), array('status'=>'enabled','no-core'=>TRUE, '#integrate' => FALSE, '#override-simulated' => TRUE));
if ($values['error_status'] != 0) return FALSE;
$contrib_extensions = array_keys($values['object']);
// Get a list of enabled core extensions.
$values = drush_invoke_process_args('pm-list', array(), array('status'=>'enabled','core'=>TRUE, '#integrate' => FALSE, '#override-simulated' => TRUE));
if ($values['error_status'] != 0) return FALSE;
$core_extensions = array_keys($values['object']);
// Make a list of modules that are not available to be enabled in the target
// version of Drupal, either because they have not been ported yet, or because
// they have been rolled into core.
$unavailable_extensions = array();
// Get the list of modules the user would like to uninstall (if any).
$uninstall_extensions = drush_get_option('uninstall', '');
if ($uninstall_extensions == "all") {
$uninstall_extensions = $contrib_extensions;
}
else {
$uninstall_extensions = explode(',', $uninstall_extensions);
}
// WARN: Tell the user about any special situations that might exist with contrib modules.
$project_download_list = array();
$extension_info = drush_pm_get_extensions();
if (!empty($contrib_extensions) && !drush_get_option('core-only')) {
// Make a list of all of the extensions to download. We will
// download everything in the contrib extension list, but we
// will skip projects that already exist.
$special_projects = drush_upgrade_project_map($target_version);
$in_core = array();
$special_warning = array();
foreach ($contrib_extensions as $extension_name) {
// Only check extensions that are NOT in our uninstall list.
if (!in_array($extension_name, $uninstall_extensions) && array_key_exists($extension_name, $extension_info)) {
$project = $extension_info[$extension_name]->info['project'];
// Check our lookup table to see if a project has been renamed.
if (array_key_exists($project, $special_projects['project-remap'])) {
$project = $special_projects['project-remap'][$project];
}
// If the module has been rolled into core in the next major release of
// Drupal, then we do not need to download it. Add it to an array for
// reporting purposes.
if ((in_array($project, $special_projects['projects-in-core'])) || (in_array($extension_name, $special_projects['modules-in-core']))) {
$in_core[$extension_name] = $extension_name;
// Might some of these need to be enabled?
$unavailable_extensions[] = $extension_name;
}
elseif (($extension_info[$extension_name]->type == 'module') && !is_dir($destination_core . '/sites/all/modules/' . $project) && !is_dir($destination_core . '/' . $destination_conf_path . '/modules/' . $project)) {
$project_download_list[$project][] = $extension_name;
}
// If there is a special warning about a project, then add it
// to the warning list for later reporting.
if (array_key_exists($project . ':' . $extension_name, $special_projects['warning'])) {
$special_warning[$project] = $special_projects['warning'][$project . ':' . $extension_name];
}
elseif ((array_key_exists($project, $special_projects['warning'])) && (!array_key_exists($project, $special_warning))) {
$special_warning[$project] = $special_projects['warning'][$project];
}
}
}
// Consider each project from the contrib extensions and check with PM to see if there is
// a recommended release available to download. If there is NO release available, then
// we are not ready to upgrade (but still can, without that project); if there is no RECOMMENDED
// release, then we might not be ready to upgrade (but still can, with a non-recommended release).
if (!empty($project_download_list)) {
$result = drush_invoke_sitealias_args(array('root' => '', 'uri' => ''), 'pm-releases', array_keys($project_download_list), array('default-major' => $target_version, '#integrate' => FALSE, '#override-simulated' => TRUE));
$project_releases = $result['object'];
foreach ($project_download_list as $project => $extension_list) {
if (!array_key_exists($project, $project_releases)) {
drush_log(dt('The project !project has no releases in version !version', array('!project' => $project, '!version' => $target_version)), 'warning');
$unavailable_extensions = array_merge($unavailable_extensions, $extension_list);
$ready_to_upgrade = FALSE;
}
else {
if (empty($project_releases[$project]['recommended'])) {
drush_log(dt('The project !project has no recommended release in version !version', array('!project' => $project, '!version' => $target_version)), 'warning');
$ready_to_upgrade = 'maybe';
}
}
}
}
// Print out some messages about projects that migrated to core, or modules that will require special processing.
if (!empty($in_core)) {
drush_log(dt('The following contrib modules were enabled in your Drupal site, but are now standard in core: !in_core. These modules may need to be reconfigured after the upgrade is complete.', array('!in_core' => implode(', ', $in_core))), 'ok');
}
foreach ($special_warning as $project => $warning) {
if ($warning === TRUE) {
$warning = 'Please see !project_page and !source for more information on how to do this.';
}
if ($warning === FALSE) {
$warning = 'So far there is no indication of when a migration path will be provided. Check !project_page for updates.';
$ready_to_upgrade = 'maybe';
}
drush_log(dt("You are using the project !project, which requires data migration or other special processing. $warning", array('!project' => $project, '!project_page' => 'http://drupal.org/project/' . $project, '!source' => $special_projects['source'])), 'warning');
}
}
// CONFIRM: Ask the user before overwriting an exsiting site, and ask if an upgrade is really decided if the site is not ready yet.
// Summarize whether or not there is a good chance that the site can be upgraded.
if ($ready_to_upgrade !== TRUE) {
drush_log(dt("Based on the contrib modules enabled in this site, it is possible that the site-upgrade command might fail. See warnings above."), (($ready_to_upgrade === FALSE) ? 'warning' : 'notice'));
}
// Check to see what we should do if the target Drupal folder already exists.
$options = array(
'replace' => dt("Delete the existing site and start over."),
'reuse' => dt("Re-use the existing code, re-import the database from the source site and run updatedb again."),
);
$selection = NULL;
foreach ($options as $option => $msg) {
if (drush_get_option($option, FALSE)) {
$selection = $option;
}
}
if (!isset($selection) && (file_exists($destination_core))) {
$selection = drush_choice($options, dt("Drupal site already exists at !root. Would you like to:", array('!root' => $destination_core)));
if (!$selection) {
return drush_user_abort();
}
}
elseif($ready_to_upgrade !== TRUE) {
if (!drush_confirm('Would you like to continue anyway?')) {
return drush_user_abort();
}
}
// User has already been prompted; skip further confirms.
drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
// We need to know where our destination settings file is regardless of which
// code path we take; therefore, we will precompute it here.
$settings_destination = $destination_core . '/' . $destination_conf_path . '/settings.php';
// STEP 1: Download the next major version of Drupal.
if (($selection == 'replace') || (!is_dir($destination_core))) {
drush_upgrade_download_drupal($target_version, $destination_core);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
drush_upgrade_copy_settings($target_alias, $settings_destination);
}
else {
// Move sites/all/modules and $conf_path()/modules out of the way
// so that updatedb can be run on core only.
if (_drush_upgrade_preserve_modules($destination_core) === FALSE) {
return FALSE;
}
}
// 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.
// We still have DRUSH_AFFIRMATIVE set from above, so this will not prompt.
drush_set_option('create-db', TRUE);
drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
drush_invoke('sql-sync', '@self', $target_key);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
if (!empty($contrib_extensions)) {
$target_alias_databases = sitealias_get_databases_from_record($target_alias);
$modify_site_conf_path = NULL;
// Make an alias record that uses the CODE from @self and the DATABASE from $target.
// Since we just did an sql-sync from @self to @target, we can use this hybrid specification
// to do manipulations on the target database before runing updatedb. In brief, we are going
// to disable all contrib modules to prevent problems with updatedb.
$modify_site = array (
'root' => DRUPAL_ROOT,
'uri' => $target_alias_databases['default']['default']['database'],
);
if (!drush_get_context('DRUSH_SIMULATE')) {
// In theory, if the sql-sync worked, this should never be empty.
if (empty($modify_site['uri'])) {
return drush_set_error('DRUSH_UPGRADE_DATABASE_SPEC_UNKNOWN', dt('Failed to look up database spec for @target', array('@target' => $target_key)));
}
$modify_site_conf_path = dirname(conf_path()) . '/' . $modify_site['uri'];
$modify_site_settings = $modify_site_conf_path . '/settings.php';
drush_log('set up a fake site by copying ' . $settings_destination . ' to ' . $modify_site_settings);
if (!file_exists($modify_site_settings)) {
if ((drush_mkdir($modify_site_conf_path) === FALSE) || drush_op('copy', $settings_destination, $modify_site_settings) !== TRUE) {
return drush_set_error('DRUSH_UPGRADE_COULD_NOT_DISABLE', dt("Could not create a temporary multisite "));
}
}
}
$result = (drush_invoke_sitealias_args($modify_site, 'site-upgrade-prepare', $contrib_extensions, array('uninstall' => implode(',', $uninstall_extensions), 'yes' => TRUE, '#interactive' => TRUE)) == 0);
// Delete the temporary site now that we're done with it.
if (isset($modify_site_conf_path)) {
drush_delete_dir($modify_site_conf_path);
}
if ($result === FALSE) return FALSE;
}
// STEP 2: Call updatedb for Drupal core.
// Run update.php in a subshell. It is run on @target site whereas this request was on @self.
drush_log(dt('About to perform updatedb for Drupal core on !target', array('!target' => $target_key)), 'ok');
// When we call drush_invoke_sitealias_args in #interactive mode, the result code comes from drush_op_system, where 0 == success.
$result = drush_invoke_sitealias_args($target_alias, 'updatedb', array(), array('yes' => TRUE, '#interactive' => TRUE)) == 0;
if ($result === FALSE) {
return drush_set_error('DRUSH_DRUPAL_CORE_UPGRADE_FAILED', dt("The call to updatedb failed for Drupal core. This may be caused by a contrib module that is not yet ready for upgrade. Try running site-upgrade again with '--uninstall={module list}' to remove all contrib extensions prior to upgrade. Add modules back in until the problematic one is isolated. Please report problems in the issue queue of the module that is causing problems."));
}
drush_log(dt('updatedb complete for Drupal core'), 'ok');
// If we moved our modules out of the way, bring them back now.
_drush_upgrade_restore_preserved_modules();
// STEP 3: Download and re-enable the contrib modules.
if (!empty($contrib_extensions) && !drush_get_option('core-only')) {
$options = array('#interactive' => TRUE);
if (!empty($project_download_list)) {
$projects = implode(',', array_keys($project_download_list));
$options['projects'] = $projects;
}
// If a module changed name, then rename it prior to calling pm-enable.
foreach ($contrib_extensions as $extension_name) {
if (array_key_exists($extension_name, $special_projects['module-remap'])) {
$unavailable_extensions[] = $extension_name;
$contrib_extensions[] = $special_projects['module-remap'][$extension_name];
}
}
// Redispatch to site-upgrade-modules command, so that we will be
// bootstrapped to the target site.
$result = (drush_invoke_sitealias_args($target_alias, 'site-upgrade-modules', array_merge($core_extensions, array_diff($contrib_extensions, $unavailable_extensions, $uninstall_extensions)), $options) == 0);
}
return $result;
}
/**
* http://drupal.org/node/895314 lists projects that are now in
* core; it also has a list of projects that require special handling.
* Keep a record here too.
*
* @param $target_version
* The version of Drupal being upgraded to.
* @return @array
* - source URL to the page where more information about this upgrade can be found.
* - target-version The version of Drupal being upgraded to.
* - projects-in-core An array containing projects that were once contrib that are now in core.
* - modules-in-core An array containing modules that were once in contrib that are now in core.
* Use 'modules-in-core' in place of 'projects-in-core' only if there is a
* project where only some of its subcomponents were moved to core.
* - warning An array of warning messages to display to the user related to this upgrade.
* The key should be the name of the project that the warning applies to.
* use 'project:module' as the key if the warning only applies when the given
* module in the project is enabled; otherwise, the warning will be shown whenever
* any module in the specific project is enabled. The value can either be the
* warning string to display, or TRUE to tell the user to see the project page
* for information on the migration path, or FALSE to tell the user that there
* currently is no migration path for the given project.
*/
function drush_upgrade_project_map($target_version) {
$result = array(
'source' => '',
'target-version' => $target_version,
'projects-in-core' => array(),
'modules-in-core' => array(),
'warning' => array(),
);
if ($target_version == 7) {
$result = array(
'source' => 'http://drupal.org/node/895314',
'projects-in-core' => array(
'abssrc',
'admin_hover',
'adminrole',
'ahah_helper',
'autoload',
'automaticmenu',
'automenu',
'auto_menutitle',
'block_edit',
'block_node_visibility',
'blocks404',
'canonical_url',
'checkbox_validate',
'comment_display',
'config_perms',
'ctm',
'dbtng',
'documentation',
'drupal_queue',
'edit_term',
'elements',
'element_themehook',
'filefield',
'filter_default',
'hook_file',
'imageapi',
'imagecache',
'imagefield',
'input_format_permissions',
'jq',
'jqp',
'jquery_cookie',
'jquery_form_update',
'jquery_ui',
'jsalter',
'login_security',
'menuless_nodetype',
'nodepreview_by_type',
'parallel',
'permissions_api',
'phpass',
'plugin_manager',
'plugins',
'poormanscron',
'preview',
'rdf',
'shortlink',
'simplecdn',
'simpletest',
'storage_api',
'tar',
'taxidermy',
'tinymce_ahah',
'tinymce_dragdrop',
'transaction',
'upload_element',
'upload_preview',
'url_alter',
'user_cancellation',
'user_default_filter',
'user_delete',
'vertical_tabs',
'view_unpublished',
'vocabperms',
'wysiwygcck',
),
'modules-in-core' => array(
// 'date' project is still contrib, but date_timezone, one of its modules, has moved to core. See http://drupal.org/node/868028.
'date_timezone',
),
'project-remap' => array(),
'module-remap' => array(
'content' => 'cck',
),
'warning' => array(
'token' => dt('In Drupal 7, the contrib token module handles UI, as well as field and profile tokens; all other functionality has been migrated to core.'),
'cck' => TRUE,
'content_taxonomy' => dt('The project page for this module (!project_page) says that an upgrade path will be provided for this module at a future time.'),
'date:date_api' => dt("The d6 version of the date_api module in the date project defines a table called date_formats, which is also defined by system/system.install schema in d7. If this problem has not been fixed yet, then the updatedb function will fail, and it will not be possible to upgrade to d7. If this happens, disable and uninstall the date_api module before running site-upgrade (i.e. add '--uninstall=date_api' to site-upgrade call). See http://drupal.org/node/1013034."),
'field_taxonomy' => FALSE,
'filefield' => dt('Data migration for this module will be provided by the Content Migrate submodule of cck. Enable content_migrate after upgrading; see http://drupal.org/node/781088.'),
'imagefield' => dt('Data migration for this module will be provided by the Content Migrate submodule of cck. Enable content_migrate after upgrading; see http://drupal.org/node/781088.'),
'taxonomy_delegate' => FALSE,
'taxonomy_intro' => FALSE,
'term_fields' => FALSE,
),
);
}
drush_command_invoke_all_ref('drush_upgrade_project_map_alter', $result);
return $result;
}
/**
* Prepare to upgrade; the first step is to disable all contrib modules.
*/
function drush_upgrade_site_upgrade_prepare() {
$contrib_extensions = func_get_args();
$uninstall_extensions = explode(',', drush_get_option('uninstall', ''));
// Set theme back to garland per Upgrade.txt.
variable_set('theme_default', 'garland');
// http://drupal.org/node/724102 recommends using "seven" as your admin theme. Don't switch it to garland if it is already seven.
$admin_theme = variable_get('admin_theme', NULL);
if ($admin_theme != "seven") {
variable_set('admin_theme', 'garland');
}
else {
drush_log(dt("Admin theme is already set to 'seven'."), 'ok');
}
// Disable all contrib modules per Upgrade.txt.
drush_log(dt("Disabling !list", array('!list' => implode(", ", $contrib_extensions))), 'ok');
call_user_func_array('drush_pm_disable', $contrib_extensions);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
// Uninstall any modules specified via the --uninstall flag.
if (!empty($uninstall_extensions)) {
drush_log(dt("Uninstalling !list", array('!list' => implode(", ", $uninstall_extensions))), 'ok');
call_user_func_array('drush_pm_uninstall', $uninstall_extensions);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
}
}
/**
* Upgrade all of the contrib modules of the site being upgraded.
*
* This runs bootstrapped to the TARGET site, after the new version
* of Drupal has been downloaded, and after updatedb has been run
* for Drupal core.
*/
function drush_upgrade_site_upgrade_modules() {
$extensions_to_enable = func_get_args();
$project_download_list = drush_get_option('projects', '');
if (!empty($project_download_list)) {
// Download our contrib extensions.
drush_log(dt('Download projects: !projects', array('!projects' => $project_download_list)), 'ok');
drush_set_option('destination', NULL);
// User has already been prompted; if there is no recommended release,
// we will just take the most recent.
drush_set_option('choice', '1');
call_user_func_array('drush_pm_download', explode(',', $project_download_list));
}
// Run updatedb to update all of the contrib extensions.
drush_log(dt('About to perform updatedb for extensions'), 'ok');
$result = drush_invoke_process_args('updatedb', array(), array('yes' => TRUE, '#interactive' => TRUE));
if ($result === FALSE) {
return drush_set_error('DRUSH_DRUPAL_CONTRIB_UPGRADE_FAILED', dt("The call to updatedb failed for the enabled contrib modules. Try running site-upgrade again with '--uninstall={module list}' to remove all contrib extensions prior to upgrade. Add modules back in until the problematic one is isolated. Please report problems in the issue queue of the module that is causing problems."));
}
drush_log(dt('updatedb complete for extensions'), 'ok');
// Finally, enable the modules that site-upgrade previously disabled.
// We will set the option --resolve-dependencies to pick up new modules
// that may now be required; for example, views-7.x picked up a dependency
// on ctools that views-6.x did not have. We also set DRUSH_AFFIRMATIVE,
// so everything from here on out will be processed with --yes.
drush_set_option('resolve-dependencies', TRUE);
drush_set_context('DRUSH_AFFIRMATIVE', TRUE);
$result = drush_invoke_args('pm-enable', $extensions_to_enable);
return $result;
}
/**
* Download the upgraded version of Drupal for site-upgrade.
*/
function drush_upgrade_download_drupal($target_version, $destination_core) {
// Fetch target core and place as per target alias root.
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');
if (drush_get_context('DRUSH_SIMULATE')) {
drush_log(dt("Simulated download of drupal-!version", array('!version' => $target_version)));
}
else {
drush_pm_download('drupal-' . $target_version);
if (drush_get_error()) return FALSE; // Early exit if we see an error.
// Check and see if there is a Drupal site at the target.
if (!file_exists($destination_core . '/includes/bootstrap.inc')) {
return drush_set_error('DRUSH_UPGRADE_NO_DRUPAL', dt('Drupal could not be downloaded to the target directory, @root. Move existing content out of the way first.', array('@root' => $target_alias['root'])));
}
}
}
/**
* Copy the settings.php file from the source site to the target site,
* and fix it up so that it will have its own database settings.
*/
function drush_upgrade_copy_settings(&$target_alias, $settings_destination) {
$settings_destination_folder = dirname($settings_destination);
// Create sites subdirectory in target if needed.
$settings_source = conf_path() . '/settings.php';
if (!file_exists($settings_destination_folder)) {
if (!drush_op('mkdir', $settings_destination_folder) && !drush_get_context('DRUSH_SIMULATE')) {
return drush_set_error('DRUSH_UPGRADE_MKDIR_FAILED', dt('Failed to create directory @settings_destination', array('@settings_destination' => $settings_destination_folder)));
}
}
// Copy settings.php to target.
if (!file_exists($settings_destination)) {
if (!drush_op('copy', $settings_source, $settings_destination) && !drush_get_context('DRUSH_SIMULATE')) {
return drush_set_error('DRUSH_UPGRADE_COPY_FAILED', dt('Failed to copy @source to @dest', array('@source' => $settings_source, 'dest' => $settings_destination)));
}
}
// Append new $db_url with new DB name in target's settings.php.
return drush_upgrade_fix_db_url($target_alias, $settings_destination);
}
/**
* 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'];
}
$old_databases = $GLOBALS['databases'];
if (empty($old_databases)) {
$old_databases = drush_sitealias_convert_db_from_db_url($old_url);
}
$target_alias_databases = sitealias_get_databases_from_record($target_alias);
$database_name = $target_alias_databases['default']['default']['database'];
if (empty($database_name)) {
$database_name = str_replace("@", "", $target_alias['name']) . "db";
drush_log(dt("No database name specified; defaulting to !dbname", array("!dbname" => $database_name)), 'notice');
}
$append = "\n# Added by drush site-upgrade.";
if (drush_drupal_major_version() <= 6) {
$new_url = substr($old_url, 0, strrpos(trim($old_url), '/')) . '/'. $database_name;
$append .= "\n" . '$db_url = \'' . $new_url . '\';';
$databases = drush_sitealias_convert_db_from_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) . ';';
}
// Caching the database record in the alias record allows sql-sync to work
// before updatedb is called. updatedb is what converts from a db_url to a
// DBTNG array; this conversion is required by sql-sync.
drush_sitealias_cache_db_settings($target_alias, $databases);
// Also append the new configuration options to the end of settings.php.
drush_op('file_put_contents', $settings_destination, $append, FILE_APPEND);
}
/**
* Rollback function: restore our modules if updatedb fails.
*/
function drush_upgrade_site_upgrade_rollback($target_key) {
_drush_upgrade_restore_preserved_modules();
}
/**
* Preserve existing modules. Move them out of the way prior
* to updatedb of Drupal core. We will move them back afterwards.
*/
function _drush_upgrade_preserve_modules($destination_core) {
$modules_preserve['root'] = $destination_core;
$modules_preserve['list'] = array();
$moduledir_list = array(
'sites_all_modules' => $destination_core . '/sites/all/modules',
'sites_conf_path_modules' => $destination_core . conf_path() . '/modules',
);
foreach ($moduledir_list as $moduledir_name => $moduledir) {
if (is_dir($moduledir)) {
$preserved_moduledir = drush_tempnam($moduledir_name, dirname($moduledir));
$result = drush_move_dir($moduledir, $preserved_moduledir, TRUE);
if ($result) {
$modules_preserve['list'][$moduledir] = $preserved_moduledir;
drush_log(dt('Move !src to !dest prior to updatedb on Drupal core.', array('!src' => $moduledir, '!dest' => $preserved_moduledir)), 'ok');
}
else {
return drush_set_error('DRUSH_MODULE_PRESERVE_FAILED', dt('Failed to move !src to !dest.', array('!src' => $moduledir, '!dest' => $preserved_moduledir)));
}
}
}
drush_set_context('DRUSH_MODULES_PRESERVE', $modules_preserve);
return TRUE;
}
/**
* Restore modules that were preserved by _drush_upgrade_preserve_modules.
*/
function _drush_upgrade_restore_preserved_modules() {
$modules_preserve = drush_get_context('DRUSH_MODULES_PRESERVE', array());
if (!empty($modules_preserve) && array_key_exists('list', $modules_preserve)) {
foreach ($modules_preserve['list'] as $moduledir => $preserved_moduledir) {
drush_move_dir($preserved_moduledir, $moduledir, TRUE);
}
}
drush_set_context('DRUSH_MODULES_PRESERVE', array());
}

View File

@@ -0,0 +1,187 @@
<?php
/**
* 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;
$found = FALSE;
$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);
$found = TRUE;
}
if (!$found) {
return drush_set_error('No matching variable found.');
}
}
/**
* 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');
}

View File

@@ -0,0 +1,375 @@
<?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_user_abort();
}
$ntypes = count($types);
if ($option < $ntypes) {
drush_set_option('type', $types[$option]);
}
else {
drush_set_option('severity', $option - $ntypes);
}
drush_core_watchdog_show_many();
}
/**
* Command callback.
*/
function drush_core_watchdog_show($arg = NULL) {
if (is_numeric($arg)) {
drush_core_watchdog_show_one($arg);
}
else {
drush_core_watchdog_show_many($arg);
}
}
/**
* Print a watchdog message.
*
* @param $wid
* The id of the message to show.
*/
function drush_core_watchdog_show_one($wid) {
$rsc = drush_db_select('watchdog', '*', 'wid = :wid', array(':wid' => $wid), 0, 1);
$result = drush_db_fetch_object($rsc);
if (!$result) {
return drush_set_error(dt('Watchdog message #!wid not found.', array('!wid' => $wid)));
}
$result = core_watchdog_format_result($result, TRUE);
foreach ($result as $key => $value) {
$uc = ucfirst($key);
$upper->$uc = $value;
}
drush_print_table(drush_key_value_to_array_table($upper));
print "\n";
}
/**
* Print a table of watchdog messages.
*
* @param $filter
* String to filter the message's text by.
*/
function drush_core_watchdog_show_many($filter = NULL) {
$count = drush_get_option('count', 10);
$type = drush_get_option('type');
$severity = drush_get_option('severity');
$tail = drush_get_option('tail', FALSE);
$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_user_abort();
}
drush_db_delete('watchdog');
drush_log(dt('All watchdog messages have been deleted.'), 'ok');
}
else if (is_numeric($arg)) {
drush_print(dt('Watchdog message #!wid will be deleted.', array('!wid' => $arg)));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
$affected_rows = drush_db_delete('watchdog', 'wid=:wid', array(':wid' => $arg));
if ($affected_rows == 1) {
drush_log(dt('Watchdog message #!wid has been deleted.', array('!wid' => $arg)), 'ok');
}
else {
return drush_set_error(dt('Watchdog message #!wid does not exist.', array('!wid' => $arg)));
}
}
else {
$type = drush_get_option('type');
$severity = drush_get_option('severity');
if ((is_null($arg))&&(is_null($type))&&(is_null($severity))) {
return drush_set_error(dt('No options provided.'));
}
$where = core_watchdog_query($type, $severity, $arg, 'OR');
if ($where === FALSE) {
// Drush set error was already called by core_watchdog_query
return FALSE;
}
drush_print(dt('All messages with !where will be deleted.', array('!where' => preg_replace("/message LIKE %$arg%/", "message body containing '$arg'" , strtr($where['where'], $where['args'])))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
$affected_rows = drush_db_delete('watchdog', $where['where'], $where['args']);
drush_log(dt('!affected_rows watchdog messages have been deleted.', array('!affected_rows' => $affected_rows)), 'ok');
}
}
/**
* Build a WHERE snippet based on given parameters.
*
* @param $type
* String. Valid watchdog type.
* @param $severity
* Int or String for a valid watchdog severity message.
* @param $filter
* String. Value to filter watchdog messages by.
* @param $criteria
* ('AND', 'OR'). Criteria for the WHERE snippet.
* @return
* False or array with structure ('where' => string, 'args' => array())
*/
function core_watchdog_query($type = NULL, $severity = NULL, $filter = NULL, $criteria = 'AND') {
$args = array();
$conditions = array();
if ($type) {
$types = core_watchdog_message_types();
if (array_search($type, $types) === FALSE) {
$msg = "Unrecognized message type: !type.\nRecognized types are: !types.";
return drush_set_error('WATCHDOG_UNRECOGNIZED_TYPE', dt($msg, array('!type' => $type, '!types' => implode(', ', $types))));
}
$conditions[] = "type = :type";
$args[':type'] = $type;
}
if (!is_null($severity)) {
drush_include_engine('drupal', 'environment');
$severities = core_watchdog_severity_levels();
if (isset($severities[$severity])) {
$level = $severity;
}
elseif (($key = array_search($severity, $severities)) !== FALSE) {
$level = $key;
}
else {
$level = FALSE;
}
if ($level === FALSE) {
foreach ($severities as $key => $value) {
$levels[] = "$value($key)";
}
$msg = "Unknown severity level: !severity.\nValid severity levels are: !levels.";
return drush_set_error(dt($msg, array('!severity' => $severity, '!levels' => implode(', ', $levels))));
}
$conditions[] = 'severity = :severity';
$args[':severity'] = $level;
}
if ($filter) {
$conditions[] = "message LIKE :filter";
$args[':filter'] = '%'.$filter.'%';
}
$where = implode(" $criteria ", $conditions);
return array('where' => $where, 'args' => $args);
}
/**
* Helper function to obtain the message types based on drupal version.
*
* @return
* Array of watchdog message types.
*/
function core_watchdog_message_types() {
if (drush_drupal_major_version() == 5) {
return _watchdog_get_message_types();
}
else {
return _dblog_get_message_types();
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* @file Drush PM CVS extension
*/
/**
* Validate this package handler can run.
*/
function package_handler_validate() {
// Check cvs command exists. Disable possible output.
$debug = drush_get_context('DRUSH_DEBUG');
drush_set_context('DRUSH_DEBUG', FALSE);
$success = drush_shell_exec('cvs --version');
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('cvs executable not found.'));
}
// Check cvs_deploy is enabled. Only for bootstrapped sites.
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
if (!module_exists('cvs_deploy')) {
drush_log(dt('cvs package handler needs cvs_deploy module enabled to work properly.'), 'warning');
}
}
}
/**
* 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_download_project(&$project, $release) {
// 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 {
return 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'])));
}
}
}
/**
* 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) {
// Build the cvs command to execute.
$command = array('cvs');
// Global options.
$command[] = '-z6';
// cvs root.
$cvsroot = '-d:pserver:' . drush_get_option('cvscredentials', 'anonymous:anonymous') . '@cvs.drupal.org:/cvs/';
$cvsroot .= ($project['project_type'] == 'core')?'drupal':'drupal-contrib';
$command[] = $cvsroot;
// CVS command ("cvs method").
$cvsmethod = drush_get_option('cvsmethod', FALSE);
if (empty($cvsmethod)) {
$cvsmethod = 'checkout';
// If we have an existing working copy we update.
if (is_dir($project['full_project_path'] . '/CVS')) {
$cvsmethod = 'update';
}
}
$command[] = $cvsmethod;
// CVS command options.
$cvsparams = drush_get_option('cvsparams', FALSE);
// common options for any command.
$command[] = '-r '. $release['tag'];
// command specific options.
if ($cvsmethod == 'checkout') {
// checkout dir.
$command[] = '-d ' . $project['project_dir'];
// path to cvs 'module' to check out.
if ($project['project_type'] == 'core') {
$command[] = $project['name']; // drupal
}
else {
// strtr for 'theme engine' type.
$command[] = 'contributions/' . strtr($project['project_type'], ' ' ,'-') . 's/' . $project['name'];
}
}
else {
if ($cvsparams === FALSE) {
// 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 a VCS is competent enough to deal with this!
$reserved_files = drush_version_control_reserved_files();
$overwrite = TRUE;
foreach ($reserved_files as $file) {
if (file_exists($project['full_project_path'] . '/' . $file)) {
$overwrite = FALSE;
break;
}
}
$command[] = $overwrite?'-dPC':'-dP';
}
// Directory to work on.
$command[] = $project['project_dir'];
}
// CVS only accepts relative paths. We will cd in the checkout path right
// before running the cvs command.
if (!drush_shell_cd_and_exec($project['base_project_path'], implode(' ', $command))) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', dt('Unable to !op !project from cvs.drupal.org.', array('!op' => $cvsmethod, '!project' => $project['name'])));
}
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*/
function package_handler_post_download($project) {
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* @file Drush PM drupal.org Git extension.
*/
/**
* Validate this package handler can run.
*/
function package_handler_validate() {
// Check git command exists. Disable possible output.
$debug = drush_get_context('DRUSH_DEBUG');
drush_set_context('DRUSH_DEBUG', FALSE);
$success = drush_shell_exec('git --version');
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.'));
}
// Check git_deploy is enabled. Only for bootstrapped sites.
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
if (!module_exists('git_deploy')) {
drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), 'warning');
}
}
}
/**
* Download a project.
*
* @param $request
* The project array with name, base and full (final) paths.
* @param $release
* The release details array from drupal.org.
*/
function package_handler_download_project(&$request, $release) {
if ($username = drush_get_option('gitusername')) {
// Uses SSH, which enables pushing changes back to git.drupal.org.
$repository = $username . '@git.drupal.org:project/' . $request['name'] . '.git';
}
else {
$repository = 'git://git.drupal.org/project/' . $request['name'] . '.git';
}
$request['repository'] = $repository;
$tag = $release['tag'];
// If the --cache option was given, create a new git reference cache of the
// remote repository, or update the existing cache to fetch recent changes.
if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) {
$gitcache = $cachedir . '/git';
$projectcache = $gitcache . '/' . $request['name'] . '.git';
drush_mkdir($gitcache);
// Setup a new cache, if we don't have this project yet.
if (!file_exists($projectcache)) {
// --mirror works similar to --bare, but retrieves all tags, local
// branches, remote branches, and any other refs (notes, stashes, etc).
// @see http://stackoverflow.com/questions/3959924
$command = 'git clone --mirror';
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
$command .= ' %s %s';
drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git');
}
// If we already have this project, update it to speed up subsequent clones.
else {
// A --mirror clone is fully synchronized with `git remote update` instead
// of `git fetch --all`.
// @see http://stackoverflow.com/questions/6150188
drush_shell_cd_and_exec($projectcache, 'git remote update');
}
$gitcache = $projectcache;
}
// Clone the repo into its appropriate target location.
$command = 'git clone';
$command .= ' ' . drush_get_option('gitcloneparams');
if (drush_get_option('cache')) {
$command .= ' --reference ' . drush_escapeshellarg($gitcache);
}
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
$command .= ' ' . drush_escapeshellarg($repository);
$command .= ' ' . drush_escapeshellarg($request['full_project_path']);
if (!drush_shell_exec($command)) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
}
// Check if the 'tag' from the release feed is a tag or a branch.
// If the tag exists, git will return it
if (!drush_shell_cd_and_exec($request['full_project_path'], 'git tag -l ' . drush_escapeshellarg($tag))) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to clone project !name from git.drupal.org.', array('!name' => $request['name'])));
}
$output = drush_shell_exec_output();
if (isset($output[0]) && ($output[0] == $tag)) {
// If we want a tag, simply checkout it. The checkout will end up in
// "detached head" state.
$command = 'git checkout ' . drush_get_option('gitcheckoutparams');
$command .= ' ' . drush_escapeshellarg($tag);
if (!drush_shell_cd_and_exec($request['full_project_path'], $command)) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
}
else {
// Else, we want to checkout a branch.
// First check if we are not already in the correct branch.
if (!drush_shell_cd_and_exec($request['full_project_path'], 'git symbolic-ref HEAD')) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
$output = drush_shell_exec_output();
$current_branch = preg_replace('@^refs/heads/@', '', $output[0]);
// If we are not on the correct branch already, switch to the correct one.
if ($current_branch != $tag) {
$command = 'git checkout';
$command .= ' ' . drush_get_option('gitcheckoutparams');
$command .= ' --track ' . drush_escapeshellarg('origin/' . $tag) . ' -b ' . drush_escapeshellarg($tag);
if (!drush_shell_cd_and_exec($request['full_project_path'], $command)) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
}
}
return TRUE;
}
/**
* Update a project (so far, only modules are supported).
*
* @param $request
* The project array with name, base and full (final) paths.
* @param $release
* The release details array from drupal.org.
*/
function package_handler_update_project($request, $release) {
drush_log('Updating project ' . $request['name'] . ' ...');
$commands = array();
if ($release['version_extra'] == 'dev') {
// Update the branch of the development repository.
$commands[] = 'git pull';
$commands[] = drush_get_option('gitpullparams');
}
else {
// Use a stable repository.
$commands[] = 'git fetch';
$commands[] = drush_get_option('gitfetchparams');
$commands[] = ';';
$commands[] = 'git checkout';
$commands[] = drush_get_option('gitcheckoutparams');
$commands[] = $release['version'];
}
if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupal.org.');
}
return TRUE;
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*
* Here we add the project as a git submodule.
*/
function package_handler_post_download($project) {
if (drush_get_option('gitsubmodule', FALSE)) {
// Obtain the superproject path, then add as submodule.
if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) {
$output = drush_shell_exec_output();
$superproject = $output[0];
// Add the downloaded project as a submodule of its git superproject.
$command = array();
$command[] = 'git submodule add';
$command[] = drush_get_option('gitsubmoduleaddparams');
$command[] = $project['repository'];
// We need the submodule relative path.
$command[] = substr($project['full_project_path'], strlen($superproject) + 1);
if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject)));
}
}
else {
return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path']))));
}
}
}

View File

@@ -0,0 +1,104 @@
<?php
/**
* @file Drush PM Wget extension
*/
/**
* Validate this package handler can run.
*/
function package_handler_validate() {
// Check wget or curl command exists. Disable possible output.
$debug = drush_get_context('DRUSH_DEBUG');
drush_set_context('DRUSH_DEBUG', FALSE);
$success = drush_shell_exec('wget --version');
if (!$success) {
$success = drush_shell_exec('curl --version');
// Old version of curl shipped in darwin returns error status for --version
// and --help. Give the chance to use it.
if (!$success) {
$success = drush_shell_exec('which curl');
}
}
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('wget nor curl executables found.'));
}
}
/**
* Download a project.
*
* @param $request Array with information on the request to download.
* @param $release The release details array from drupal.org.
*/
function package_handler_download_project(&$request, $release) {
// Install profiles come in several variants. User may specify which one she wants.
if ($request['project_type'] == 'profile') {
// @todo Use xpath to get the right file url.
$files = $release['files'];
foreach ($files as $key => $file) {
if ((string)$file->variant == drush_get_option('variant', 'full') && (string)$file->archive_type == 'tar.gz') {
$release['download_link'] = (string)$file->url;
$release['mdhash'] = (string)$file->md5;
break;
}
}
}
$filename = explode('/', $release['download_link']);
$filename = array_pop($filename);
// Download the project.
if (!drush_shell_exec("wget -P . %s", $release['download_link'])) {
drush_shell_exec("curl -O %s", $release['download_link']);
}
if (file_exists($filename) || drush_get_context('DRUSH_SIMULATE')) {
drush_log("Downloading " . $filename . " was successful.");
}
else {
return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $request['base_project_path'] . ' from '. $release['download_link']);
}
// Check Md5 hash.
if (drush_op('md5_file', $filename) != $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) {
drush_set_error('DRUSH_PM_FILE_CORRUPT', "File $filename is corrupt (wrong md5 checksum).");
drush_op('unlink', $filename);
return FALSE;
}
else {
drush_log("Md5 checksum of $filename verified.");
}
// Extract the tarball.
$file_list = drush_tarball_extract($filename, $request['base_project_path'], TRUE);
drush_op('unlink', $filename);
// Move untarred directory to project_dir, if distinct.
if (($request['project_type'] == 'core') || (($request['project_type'] == 'profile') && (drush_get_option('variant', 'core') == 'core'))) {
// Obtain the dodgy project_dir for drupal core.
$project_dir = rtrim($file_list[0], DIRECTORY_SEPARATOR);
if ($request['project_dir'] != $project_dir) {
$path = $request['base_project_path'];
drush_move_dir($path . '/'. $project_dir, $path . '/' . $request['project_dir']);
}
}
return TRUE;
}
/**
* This is an alias of the download function, since they are identical
*/
function package_handler_update_project(&$request, $release) {
return package_handler_download_project($request, $release);
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*/
function package_handler_post_download($project) {
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,145 @@
<?php
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;
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.
*
* @see update_get_available().
* @see update_manual_status().
*
* @return An array containing remote and local versions for all installed
* projects
*/
function _pm_get_update_info($projects) {
// Force to invalidate some caches that are only cleared
// when visiting update status report page. This allow to detect changes in
// .info files.
_update_cache_clear('update_project_data');
_update_cache_clear('update_project_projects');
// From update_get_available(): Iterate all projects and create a fetch task
// for those we have no information or is obsolete.
module_load_include('inc', 'update', 'update.compare');
$available = _update_get_cached_available_releases();
$update_projects = update_get_projects();
foreach ($update_projects as $key => $project) {
if (empty($available[$key])) {
update_create_fetch_task($project);
continue;
}
if ($project['info']['_info_file_ctime'] > $available[$key]['last_fetch']) {
$available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
}
if (empty($available[$key]['releases'])) {
$available[$key]['fetch_status'] = UPDATE_FETCH_PENDING;
}
if (!empty($available[$key]['fetch_status']) && $available[$key]['fetch_status'] == UPDATE_FETCH_PENDING) {
update_create_fetch_task($project);
}
}
// Set a batch to process all pending tasks.
$batch = array(
'operations' => array(
array('update_fetch_data_batch', array()),
),
'finished' => 'update_fetch_data_finished',
'file' => drupal_get_path('module', 'update') . '/update.fetch.inc',
);
batch_set($batch);
drush_backend_batch_process();
// Calculate update status data.
$available = _update_get_cached_available_releases();
$data = update_calculate_project_data($available);
foreach ($data as $project_name => $project) {
// Discard custom projects.
if ($project['status'] == UPDATE_UNKNOWN) {
unset($data[$project_name]);
continue;
}
// Allow to update disabled projects.
if (in_array($project['project_type'], array('module-disabled', 'theme-disabled'))) {
$data[$project_name]['project_type'] = substr($project['project_type'], 0, strpos($project['project_type'], '-'));
}
// Set 'includes' key to all extensions. By default it only contain enabled
// extensions and _pm_get_project_path() needs all of them.
$data[$project_name]['includes'] = drupal_map_assoc($projects[$project_name]['extensions']);
// Store all releases, not just the ones selected by update.module.
$data[$project_name]['releases'] = $available[$project_name]['releases'];
}
$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;
}

View File

@@ -0,0 +1,100 @@
<?php
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) {
// We force a refresh if the cache is not available.
if (!cache_get('update_status_info', 'cache')) {
_pm_refresh();
}
$info = update_status_get_available(TRUE);
// Force to invalidate some update_status caches that are only cleared
// when visiting update status report page.
_update_status_cache_clear('update_status_project_data');
_update_status_cache_clear('update_status_project_projects');
$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[$project_name]['modules'] = drupal_map_assoc($projects[$project_name]['extensions']);
}
$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;
}

View File

@@ -0,0 +1,103 @@
<?php
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;
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) {
// We force a refresh if the cache is not available.
if (!cache_get('update_available_releases', 'cache_update')) {
_pm_refresh();
}
$info = update_get_available(TRUE);
// Force to invalidate some update_status caches that are only cleared
// when visiting update status report page.
_update_cache_clear('update_project_data');
_update_cache_clear('update_project_projects');
$data = update_calculate_project_data($info);
foreach ($data as $project_name => $project) {
// Discard custom projects.
if ($project['status'] == UPDATE_UNKNOWN) {
unset($data[$project_name]);
continue;
}
if (in_array($project['project_type'], array('disabled-module', 'disabled-theme'))) {
$data[$project_name]['project_type'] = substr($project['project_type'], strpos($project['project_type'], '-') + 1);
}
$data[$project_name]['includes'] = drupal_map_assoc($projects[$project_name]['extensions']);
}
$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;
}

View File

@@ -0,0 +1,640 @@
<?php
/**
* 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');
// Find only security updates?
$security_only = drush_get_option('security-only');
// Get specific requests.
$requests = _convert_csv_to_array(func_get_args());
// Parse out project name and version.
$requests = pm_parse_project_version($requests);
// Get installed extensions and projects.
$extensions = drush_get_extensions();
$projects = drush_get_projects($extensions);
// Get update status information.
$update_info = _pm_get_update_info($projects);
// Process locks specified on the command line.
$locked_list = drush_pm_update_lock($update_info, drush_get_option_list('lock'), drush_get_option_list('unlock'), drush_get_option('lock-message'));
foreach ($extensions as $name => $extension) {
// Add an item to $update_info for each enabled extension which was obtained
// from cvs or git and its project is unknown (because of cvs_deploy or
// git_deploy is not enabled).
if (!isset($extension->info['project'])) {
if ((isset($extension->vcs)) && ($extension->status)) {
$update_info[$name] = array(
'title' => $extension->info['name'],
'existing_version' => 'Unknown',
'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_PACKAGED,
'status_msg' => dt('Project was not packaged by drupal.org but obtained from !vcs. You need to enable !vcs_deploy module', array('!vcs' => $extension->vcs))
);
// The user may have requested to update a project matching this
// extension. If it was by coincidence or error we don't mind as we've
// already added an item to $update_info. Just clean up $requests.
if (isset($requests[$name])) {
unset($requests[$name]);
}
}
}
// Aditionally if the extension name is distinct to the project name and
// the user asked to update the extension, fix the request.
elseif ((isset($requests[$name])) && ($extension->name != $extension->info['project'])) {
$requests[$extension->info['project']] = $requests[$name];
unset($requests[$name]);
}
}
// Add an item to $update_info for each request not present in $update_info.
foreach ($requests as $name => $request) {
if (!isset($update_info[$name])) {
// Disabled projects.
if ((isset($projects[$name])) && ($projects[$name]['status'] == 0)) {
$update_info[$name] = array(
'title' => $name,
'existing_version' => $projects[$name]['version'],
'status' => DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE
);
unset($requests[$name]);
}
// At this point we are unable to find matching installed project.
// It does not exist at all or it is mispelled,...
else {
$update_info[$name] = array(
'title' => $name,
'existing_version' => 'Unknown',
'status'=> DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND,
);
}
}
}
// If specific versions were requested, match the requested release.
foreach ($requests as $name => $request) {
if (!empty($request['version'])) {
$release = pm_get_release($request, $update_info[$name]);
if (!$release) {
$update_info[$name]['status'] = DRUSH_PM_REQUESTED_VERSION_NOT_FOUND;
}
else if ($release['version'] == $update_info[$name]['existing_version']) {
$update_info[$name]['status'] = DRUSH_PM_REQUESTED_CURRENT;
}
else {
$update_info[$name]['status'] = DRUSH_PM_REQUESTED_UPDATE;
}
// Set the candidate version to the requested release.
$update_info[$name]['candidate_version'] = $release['version'];
}
}
// Table headers.
$rows[] = array(dt('Name'), dt('Installed version'), dt('Proposed version'), dt('Status'));
// Process releases, notifying user of status and
// building a list of proposed updates.
$updateable = pm_project_filter($update_info, $rows, $security_only);
// Pipe preparation.
if (drush_get_context('DRUSH_PIPE')) {
$pipe = "";
foreach($updateable as $project) {
$pipe .= $project['name']. " ";
$pipe .= $project['existing_version']. " ";
$pipe .= $project['candidate_version']. " ";
$pipe .= str_replace(' ', '-', pm_update_filter($project)). "\n";
}
drush_print_pipe($pipe);
// Automatically curtail update process if in pipe mode.
$updateable = array();
}
$tmpfile = drush_tempnam('pm-updatecode.');
$last = pm_update_last_check();
drush_print(dt('Update information last refreshed: ') . ($last ? format_date($last) : dt('Never')));
drush_print();
drush_print(dt("Update status information on all installed and enabled Drupal projects:"));
drush_print_table($rows, TRUE, array(3 => 40), $tmpfile);
$contents = file_get_contents($tmpfile);
drush_print($contents);
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]);
}
}
}
// Prevent update of core if --no-core was specified.
if (isset($updateable['drupal']) && drush_get_option('no-core', FALSE)) {
unset($updateable['drupal']);
drush_print(dt('Skipping core update (--no-core specified).'));
}
// If there are any locked projects that were not requested, then remove them.
if (!empty($locked_list)) {
foreach ($updateable as $name => $project) {
if ((isset($locked_list[$name])) && (!isset($requests[$name]))) {
unset($updateable[$name]);
}
}
}
// First check to see if there is a newer drush.
$drush_update_available = NULL;
if (drush_get_option('self-update', TRUE)) {
$drush_update_available = drush_check_self_update();
}
// Do no updates in simulated mode.
if (drush_get_context('DRUSH_SIMULATE')) {
return drush_log(dt('No action taken in simulated mode.'), 'ok');
return TRUE;
}
$core_update_available = FALSE;
if (isset($updateable['drupal'])) {
$drupal_project = $updateable['drupal'];
unset($update_info['drupal']);
unset($updateable['drupal']);
// At present we need to update drupal core after non-core projects
// are updated.
if (empty($updateable)) {
return _pm_update_core($drupal_project, $tmpfile);
}
// If there are modules other than drupal core enabled, then update them
// first.
else {
$core_update_available = TRUE;
if ($drupal_project['status'] == UPDATE_NOT_SECURE) {
drush_print(dt("NOTE: A security update for the Drupal core is available."));
}
else {
drush_print(dt("NOTE: A code update for the Drupal core is available."));
}
drush_print(dt("Drupal core will be updated after all of the non-core modules are updated.\n"));
}
}
// If there are no releases to update, then print a final
// exit message. Supress the message if we already printed
// a message about a drush update being available.
if (empty($updateable)) {
if ($drush_update_available === TRUE) {
return FALSE;
}
if ($security_only) {
return drush_log(dt('No security updates available.'), 'ok');
}
else {
return drush_log(dt('No code updates available.'), 'ok');
}
}
// Offer to update to the identified releases.
if (!pm_update_packages($updateable, $tmpfile)) {
return FALSE;
}
// After projects are updated we can update core.
if ($core_update_available) {
drush_print();
return _pm_update_core($drupal_project, $tmpfile);
}
}
/**
* Update drupal core, following interactive confirmation from the user.
*
* @param $project
* The drupal project information from the drupal.org update service,
* copied from $update_info['drupal']. @see drush_pm_updatecode.
*/
function _pm_update_core(&$project, $tmpfile) {
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."));
drush_print();
if (drush_get_option('notes', FALSE)) {
drush_print('Obtaining release notes for above projects...');
_drush_pm_releasenotes(array('drupal'), TRUE, $tmpfile);
}
if(!drush_confirm(dt('Do you really want to continue?'))) {
drush_print(dt('Rolling back all changes. Run again with --no-core to update modules only.'));
return drush_user_abort();
}
// We need write permission on $drupal_root.
if (!is_writable($drupal_root)) {
return drush_set_error('DRUSH_PATH_NO_WRITABLE', dt('Drupal root path is not writable.'));
}
// Create a directory 'core' if it does not already exist.
$project['path'] = 'drupal-' . $project['candidate_version'];
$project['full_project_path'] = $drupal_root . '/' . $project['path'];
if (!is_dir($project['full_project_path'])) {
drush_mkdir($project['full_project_path']);
}
// Create a list of directories to exclude from the update process.
$skip_list = array('sites', $project['path']);
// Add non-writable directories: we can't move them around.
// We will also use $items_to_test later for $version_control check.
$items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE);
foreach (array_keys($items_to_test) as $item) {
if (is_dir($item) && !is_writable($item)) {
$skip_list[] = $item;
unset($items_to_test[$item]);
}
}
$project['skip_list'] = $skip_list;
// Move all files and folders in $drupal_root to the new 'core' directory
// except for the items in the skip list
_pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);
// Set a context variable to indicate that rollback should reverse
// the _pm_update_move_files above.
drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);
if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
return FALSE;
}
$project['base_project_path'] = dirname($project['full_project_path']);
// Check we have a version control system, and it clears its pre-flight.
if (!$version_control->pre_update($project, $items_to_test)) {
return FALSE;
}
// Package handlers want the project directory in project_dir.
$project['project_dir'] = $project['path'];
// Update core.
if (pm_update_project($project, $version_control) === FALSE) {
return FALSE;
}
// Take the updated files in the 'core' directory that have been updated,
// and move all except for the items in the skip list back to
// the drupal root
_pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
drush_delete_dir($project['full_project_path']);
// Version control engines expect full_project_path to exist and be accurate.
$project['full_project_path'] = $project['base_project_path'];
// If there is a backup target, then find items
// in the backup target that do not exist at the
// drupal root. These are to be moved back.
if (array_key_exists('backup_target', $project)) {
_pm_update_move_files($project['backup_target'], $drupal_root, $project['skip_list'], FALSE);
_pm_update_move_files($project['backup_target'] . '/profiles', $drupal_root . '/profiles', array('default'), FALSE);
}
pm_update_complete($project, $version_control);
return TRUE;
}
/**
* Move some files from one location to another
*/
function _pm_update_move_files($src_dir, $dest_dir, $skip_list, $remove_conflicts = TRUE) {
$items_to_move = drush_scan_directory($src_dir, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'filename', 0, TRUE);
foreach ($items_to_move as $filename => $info) {
if ($remove_conflicts) {
drush_delete_dir($dest_dir . '/' . basename($filename));
}
if (!file_exists($dest_dir . '/' . basename($filename))) {
$move_result = drush_move_dir($filename, $dest_dir . '/' . basename($filename));
}
}
return TRUE;
}
/**
* Update projects according to an array of releases and print the release notes
* for each project, following interactive confirmation from the user.
*
* @param $update_info
* An array of projects from the drupal.org update service, with an additional
* array key candidate_version that specifies the version to be installed.
*/
function pm_update_packages($update_info, $tmpfile) {
drush_include_engine('package_handler', drush_get_option('package-handler', 'wget'));
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
$print = '';
$status = array();
foreach($update_info as $project) {
$print .= $project['title'] . " [" . $project['name'] . '-' . $project['candidate_version'] . "], ";
$status[$project['status']] = $project['status'];
}
// We print the list of the projects that need to be updated.
if (isset($status[UPDATE_NOT_SECURE])) {
if (isset($status[UPDATE_NOT_CURRENT])) {
$title = (dt('Security and code updates will be made to the following projects:'));
}
else {
$title = (dt('Security updates will be made to the following projects:'));
}
}
else {
$title = (dt('Code updates will be made to the following projects:'));
}
$print = "$title " . (substr($print, 0, strlen($print)-2));
drush_print($print);
file_put_contents($tmpfile, "\n\n$print\n\n", FILE_APPEND);
// Print the release notes for projects to be updated.
if (drush_get_option('notes', FALSE)) {
drush_print('Obtaining release notes for above projects...');
_drush_pm_releasenotes(array_keys($update_info), TRUE, $tmpfile);
}
// We print some warnings before the user confirms the update.
drush_print();
if (drush_get_option('no-backup', FALSE)) {
drush_print(dt("Note: You have selected to not store backups."));
}
else {
drush_print(dt("Note: A backup of your project will be stored to backups directory if it is not managed by a supported version control system."));
drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.'));
}
if(!drush_confirm(dt('Do you really want to continue with the update process?'))) {
return drush_user_abort();
}
// Now we start the actual updating.
foreach($update_info as $project) {
if (empty($project['path'])) {
return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'])));
}
drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));
// 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;
}
// Package handlers want the name of the directory in project_dir.
// It may be different to the project name for pm-download.
// Perhaps we want here filename($project['full_project_path']).
$project['project_dir'] = $project['name'];
// Run update on one project.
if (pm_update_project($project, $version_control) === FALSE) {
return FALSE;
}
pm_update_complete($project, $version_control);
}
return TRUE;
}
/**
* Update one project -- a module, theme or Drupal core.
*
* @param $project
* The project to upgrade. $project['full_project_path'] must be set
* to the location where this project is stored.
*/
function pm_update_project($project, $version_control) {
// 1. If the version control engine is a proper vcs we need to remove project
// files in order to not have orphan files after update.
// 2. If the package-handler is cvs or git, it will remove upstream removed
// files and no orphans will exist after update.
// So, we must remove all files previous update if the directory is not a
// working copy of cvs or git but we don't need to remove them if the version
// control engine is backup, as it did already move the project out to the
// backup directory.
if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
// Find and unlink all files but the ones in the vcs control directories.
$skip_list = array('.', '..');
$skip_list = array_merge($skip_list, drush_version_control_reserved_files());
drush_scan_directory($project['full_project_path'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
}
// Add the project to a context so we can roll back if needed.
$updated = drush_get_context('DRUSH_PM_UPDATED');
$updated[] = $project;
drush_set_context('DRUSH_PM_UPDATED', $updated);
if (!package_handler_update_project($project, $project['releases'][$project['candidate_version']])) {
return drush_set_error('DRUSH_PM_UPDATING_FAILED', dt('Updating project !project failed. Attempting to roll back to previously installed version.', array('!project' => $project['name'])));
}
// If the version control engine is a proper vcs we also need to remove
// orphan directories.
if (($version_control->engine != 'backup') && (drush_get_option('package-handler', 'wget') == 'wget')) {
$files = drush_find_empty_directories($project['full_project_path'], $version_control->reserved_files());
array_map('drush_delete_dir', $files);
}
return TRUE;
}
/**
* Run the post-update hooks after updatecode is complete for one project.
*/
function pm_update_complete($project, $version_control) {
drush_print(dt('Project !project was updated successfully. Installed version is now !version.', array('!project' => $project['name'], '!version' => $project['candidate_version'])));
drush_command_invoke_all('pm_post_update', $project['name'], $project['releases'][$project['candidate_version']]);
$version_control->post_update($project);
}
function drush_pm_updatecode_rollback() {
$projects = array_reverse(drush_get_context('DRUSH_PM_UPDATED', array()));
foreach($projects as $project) {
drush_log(dt('Rolling back update of !project code ...', array('!project' => $project['title'])));
// Check we have a version control system, and it clears it's pre-flight.
if (!$version_control = drush_pm_include_version_control($project['path'])) {
return FALSE;
}
$version_control->rollback($project);
}
// Post rollback, we will do additional repair if the project is drupal core.
$drupal_core = drush_get_context('DRUSH_PM_DRUPAL_CORE', FALSE);
if ($drupal_core) {
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
_pm_update_move_files($drupal_core['full_project_path'], $drupal_root, $drupal_core['skip_list']);
drush_delete_dir($drupal_core['full_project_path']);
}
}
/**
* Return an array of updateable projects and fill $rows.
*
* Array of updateable projects is obtained from calculated project update
* status and $security_only flag.
*/
function pm_project_filter(&$update_info, &$rows, $security_only) {
$updateable = array();
foreach ($update_info as $key => $project) {
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_REQUESTED_PROJECT_NOT_PACKAGED:
$status = $project['status_msg'];
break;
case DRUSH_PM_REQUESTED_VERSION_NOT_FOUND:
$status = dt('Specified version not found');
break;
case DRUSH_PM_REQUESTED_PROJECT_NOT_FOUND:
$status = dt('Specified project not found');
break;
case DRUSH_PM_REQUESTED_PROJECT_NOT_UPDATEABLE:
$status = dt('Project has no enabled extensions and can\'t be updated');
break;
default:
$status = pm_update_filter($project);
break;
}
// Special checking: if drush decides that the candidate version is older
// than the installed version, then we will set the candidate version to
// the installed version.
if (isset($project['candidate_version'], $project['releases'][$project['candidate_version']], $project['releases'][$project['existing_version']])) {
if ($project['releases'][$project['candidate_version']]['date'] < $project['releases'][$project['existing_version']]['date']) {
$project['candidate_version'] = $project['existing_version'];
}
}
if (isset($project['locked'])) {
$status = $project['locked'] . " ($status)";
}
// Persist candidate_version in $update_info (plural).
if (empty($project['candidate_version'])) {
$update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change
}
else {
$update_info[$key]['candidate_version'] = $project['candidate_version'];
}
if (!empty($project['updateable'])) {
$updateable[$key] = $project;
// Find only security updates
if ($security_only && ($project['status'] != UPDATE_NOT_SECURE)) {
unset($updateable[$key]);
}
}
$rows[] = array($project['title'], $project['existing_version'], $update_info[$key]['candidate_version'], $status);
}
return $updateable;
}
/**
* Set a release to a recommended version (if available), and set as updateable.
*/
function pm_release_recommended(&$project) {
if (isset($project['recommended'])) {
$project['candidate_version'] = $project['recommended'];
$project['updateable'] = TRUE;
}
}
/**
* Get the a best release match for a requested update.
*
* @param $request A information array for the requested project
* @param $project A project information array for this project, as returned by an update service from pm_get_extensions()
*/
function pm_get_release($request, $project) {
$minor = '';
$version_patch_changed = '';
if ($request['version']) {
// The user specified a specific version - try to find that exact version
foreach($project['releases'] as $version => $release) {
// Ignore unpublished releases.
if ($release['status'] != 'published') {
continue;
}
// Straight match
if (!isset($recommended_version) && $release['version'] == $request['version']) {
$recommended_version = $version;
}
}
}
else {
// No version specified - try to find the best version we can
foreach($project['releases'] as $version => $release) {
// Ignore unpublished releases.
if ($release['status'] != 'published') {
continue;
}
// If we haven't found a recommended version yet, put the dev
// version as recommended and hope it gets overwritten later.
// Look for the 'latest version' if we haven't found it yet.
// Latest version is defined as the most recent version for the
// default major version.
if (!isset($latest_version) && $release['version_major'] == $project['default_major']) {
$latest_version = $version;
}
if (!isset($recommended_version) && $release['version_major'] == $project['default_major']) {
if ($minor != $release['version_patch']) {
$minor = $release['version_patch'];
$version_patch_changed = $version;
}
if (empty($release['version_extra']) && $minor == $release['version_patch']) {
$recommended_version = $version_patch_changed;
}
continue;
}
}
}
if (isset($recommended_version)) {
return $project['releases'][$recommended_version];
}
else if (isset($latest_version)) {
return $project['releases'][$latest_version];
}
else {
return false;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* @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()) {
if (drush_get_option('no-backup', FALSE)) {
return TRUE;
}
if ($backup_target = $this->prepare_backup_dir()) {
if ($project['project_type'] != 'core') {
$backup_target .= '/' . $project['project_type'] . 's';
drush_mkdir($backup_target);
}
$backup_target .= '/'. $project['name'];
// Save for rollback or notifications.
$project['backup_target'] = $backup_target;
// Move or copy to backup target based in package-handler.
if (drush_get_option('package-handler', 'wget') == 'wget') {
if (drush_move_dir($project['full_project_path'], $backup_target)) {
return TRUE;
}
}
// cvs or git.
elseif (drush_copy_dir($project['full_project_path'], $backup_target)) {
return TRUE;
}
return drush_set_error('DRUSH_PM_BACKUP_FAILED', dt('Failed to backup project directory !project to !backup_target', array('!project' => $project['full_project_path'], '!backup_target' => $backup_target)));
}
}
/**
* Implementation of rollback().
*/
public function rollback($project) {
if (drush_get_option('no-backup', FALSE)) {
return;
}
if (drush_move_dir($project['backup_target'], $project['full_project_path'], TRUE)) {
return drush_log(dt("Backups were restored successfully."), 'ok');
}
return drush_set_error('DRUSH_PM_BACKUP_ROLLBACK_FAILED', dt('Could not restore backup and rollback from failed upgrade. You will need to resolve manually.'));
}
/**
* Implementation of post_update().
*/
public function post_update($project) {
if (drush_get_option('no-backup', FALSE)) {
return;
}
if ($project['backup_target']) {
drush_log(dt("Backups were saved into the directory !backup_target.", array('!backup_target' => $project['backup_target'])), 'ok');
}
}
/**
* Implementation of post_download().
*/
public function post_download($project) {
// NOOP
}
// Helper for pre_update.
public function prepare_backup_dir($subdir = NULL) {
return drush_prepare_backup_dir($subdir);
}
public static function reserved_files() {
return array();
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* @file Drush pm Bazaar extension
*/
class drush_pm_version_control_bzr implements drush_pm_version_control {
/**
* Implementation of pre_update().
*
* Check that the project or drupal core directory looks clean
*/
public function pre_update(&$project, $items_to_test = array()) {
// Bazaar needs a list of items to test within the given project.
// If $items_to_test is empty we need to force it to test the project
// directory itself --once we've cd'ed to it.
if (empty($items_to_test)) {
$items_to_test = array('.' => '.');
}
$args = array_keys($items_to_test);
array_unshift($args, 'bzr status --short' . str_repeat(' %s', count($args)));
array_unshift($args, $project['full_project_path']);
if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
$output = preg_grep('/^[\sRCP][\sNDKM][\s\*]/', drush_shell_exec_output());
if (!empty($output)) {
return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
}
}
else {
return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
/**
* Implementation of rollback().
*/
public function rollback($project) {
if (drush_shell_exec('bzr revert %s', $project['full_project_path'])) {
$output = drush_shell_exec_output();
if (!empty($output)) {
return drush_set_error('DRUSH_PM_BZR_LOCAL_CHANGES', dt("The Bazaar working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
}
}
else {
return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the Bazaar status on !path. Check that you have Bazaar \ninstalled and that this directory is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
}
/**
* Implementation of post_update().
*/
public function post_update($project) {
if ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Implementation of post_download().
*/
public function post_download($project) {
if ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Automatically add any unversioned files to Bazaar and remove any files
* that have been deleted on the file system
*/
private function sync($project) {
if (drush_get_option('bzrsync')) {
$errors = '';
$root = array();
if (drush_shell_exec('bzr status --short %s', $project['full_project_path'])) {
$output = drush_shell_exec_output();
// All paths returned by bzr status are relative to the repository root.
if (drush_shell_exec('bzr root %s', $project['full_project_path'])) {
$root = drush_shell_exec_output();
}
foreach ($output as $line) {
if (preg_match('/^\?\s+(.*)/', $line, $matches)) {
$path = $root[0] .'/'. $matches[1];
if (!drush_shell_exec('bzr add --no-recurse %s', $path)) {
$errors .= implode("\n", drush_shell_exec_output());
}
}
else if (preg_match('/^\s+D\s+(.*)/', $line, $matches)) {
$path = $root[0] .'/'. $matches[1];
if (!drush_shell_exec('bzr remove %s', $path)) {
$errors .= implode("\n", drush_shell_exec_output());
}
}
}
if (!empty($errors)) {
return drush_set_error('DRUSH_PM_BZR_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
}
}
else {
return drush_set_error('DRUSH_PM_BZR_NOT_FOUND', dt("Drush was unable to get the bzr status. Check that you have Bazaar \ninstalled and that the site is a Bazaar working copy.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
}
/**
* Automatically commit changes to the repository
*/
private function commit($project) {
if (drush_get_option('bzrcommit')) {
$message = drush_get_option('bzrmessage');
if (empty($message)) {
$message = dt("Drush automatic commit.\nProject: @name @type\nCommand: @arguments", array('@name' => $project['name'], '@type' => $project['project_type'], '@arguments' => implode(' ', $_SERVER['argv'])));
}
if (drush_shell_exec('bzr commit --message=%s %s', $message, $project['full_project_path'])) {
drush_log(dt('Project committed to Bazaar successfully'), 'ok');
}
else {
drush_set_error('DRUSH_PM_BZR_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Bazaar.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
}
}
else {
drush_print(dt("You should consider committing the new code to your Bazaar repository.\nIf this version becomes undesireable, use Bazaar to roll back."));
}
}
public static function reserved_files() {
return array('.bzr', '.bzrignore', '.bzrtags');
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* @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.
$args = array_keys($items_to_test);
array_unshift($args, 'svn status '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args)));
array_unshift($args, $project['full_project_path']);
if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
$output = preg_grep('/^[ 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 uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
}
}
else {
return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
// Check for incoming updates
$args = array_keys($items_to_test);
array_unshift($args, 'svn status -u '. drush_get_option('svnstatusparams') . str_repeat('%s ', count($args)));
array_unshift($args, $project['full_project_path']);
if (call_user_func_array('drush_shell_cd_and_exec', $args)) {
$output = preg_grep('/\*/', drush_shell_exec_output());
if (!empty($output)) {
return drush_set_error('DRUSH_PM_SVN_REMOTE_CHANGES', dt("The SVN working copy at !path appears to be out of date with the repository (see below). Please run 'svn update' to pull down changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
}
}
else {
return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn remote status on !path. Check that you have connectivity to the repository.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
/**
* Implementation of rollback().
*/
public function rollback($project) {
if (drush_shell_exec('svn revert '. drush_get_option('svnrevertparams') .' '. $project['full_project_path'])) {
$output = drush_shell_exec_output();
if (!empty($output)) {
return drush_set_error('DRUSH_PM_SVN_LOCAL_CHANGES', dt("The SVN working copy at !path appears to have uncommitted changes (see below). Please commit or revert these changes before continuing:\n!output", array('!path' => $project['full_project_path'], '!output' => implode("\n", $output))));
}
}
else {
return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
}
/**
* Implementation of post_update().
*/
public function post_update($project) {
if ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Implementation of post_download().
*/
public function post_download($project) {
if ($this->sync($project)) {
// Only attempt commit on a sucessful sync
$this->commit($project);
}
}
/**
* Automatically add any unversioned files to Subversion and remove any files
* that have been deleted on the file system
*/
private function sync($project) {
if (drush_get_option('svnsync')) {
$errors = '';
if (drush_shell_exec('svn status '. drush_get_option('svnstatusparams') .' '. $project['full_project_path'])) {
$output = drush_shell_exec_output();
foreach ($output as $line) {
if (preg_match('/^\? *(.*)/', $line, $matches)) {
if (!drush_shell_exec('svn add '. drush_get_option('svnaddparams') .' '. $matches[1])) {
$errors .= implode("\n", drush_shell_exec_output());
}
}
if (preg_match('/^\! *(.*)/', $line, $matches)) {
if (!drush_shell_exec('svn remove '. drush_get_option('svnremoveparams') .' '. $matches[1])) {
$errors .= implode("\n", drush_shell_exec_output());
}
}
}
if (!empty($errors)) {
return drush_set_error('DRUSH_PM_SVN_SYNC_PROBLEMS', dt("Problems were encountered adding or removing files to/from this SVN working copy.\nThe specific errors are below:\n!errors", array('!errors' => $errors)));
}
}
else {
return drush_set_error('DRUSH_PM_SVN_NOT_FOUND', dt("Drush was unable to get the svn status on !path. Check that you have Subversion \ninstalled and that this directory is a subversion working copy.\nThe specific errors are below:\n!errors", array('!path' => $project['full_project_path'], '!errors' => implode("\n", drush_shell_exec_output()))));
}
return TRUE;
}
}
/**
* Automatically commit changes to the repository
*/
private function commit($project) {
if (drush_get_option('svncommit')) {
$message = drush_get_option('svnmessage');
if (empty($message)) {
$message = dt("Drush automatic commit: \n") . implode(' ', $_SERVER['argv']);
}
if (drush_shell_exec('svn commit '. drush_get_option('svncommitparams') .' -m "'. $message .'" '. $project['full_project_path'])) {
drush_log(dt('Project committed to Subversion successfully'), 'ok');
}
else {
drush_set_error('DRUSH_PM_SVN_COMMIT_PROBLEMS', dt("Problems were encountered committing your changes to Subversion.\nThe specific errors are below:\n!errors", array('!errors' => implode("\n", drush_shell_exec_output()))));
}
}
else {
drush_print(dt("You should consider committing the new code to your Subversion repository.\nIf this version becomes undesireable, use Subversion to roll back."));
}
}
public static function reserved_files() {
return array('.svn');
}
}

View File

@@ -0,0 +1,999 @@
<?php
/**
* @file Drush sql commands
*/
/**
* Implementation of hook_drush_help().
*/
function sql_drush_help($section) {
switch ($section) {
case 'meta:sql:title':
return dt('SQL commands');
case 'meta:sql:summary':
return dt('Examine and modify your Drupal database.');
}
}
/**
* 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-drop'] = array(
'description' => 'Drop all tables in a given database.',
'arguments' => array(
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION,
'options' => array(
'yes' => 'Skip confirmation and proceed.',
'result-file' => 'Save to a file. The file should be relative to Drupal root. Recommended.',
) + $options,
'topics' => array('docs-policy'),
);
$items['sql-conf'] = array(
'description' => 'Print database connection details using print_r().',
'hidden' => TRUE,
'arguments' => array(
'all' => 'Show all database connections, instead of just one.',
'show-passwords' => 'Show database password.',
),
'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 or equivalent.',
'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. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.',
'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.',
'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.',
) + $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.',
'`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
'drush sql-query --input-file=example.sql' => 'Alternate way to import sql statements from a file.',
),
'arguments' => array(
'query' => 'An SQL query. Ignored if \'file\' is provided.',
),
'options' => array(
'result-file' => 'Save to a file. The file should be relative to Drupal root. Optional.',
'input-file' => 'Path to a file containing the SQL to be run.',
'extra' => 'Add custom options to the mysql command.',
) + $options,
'aliases' => array('sqlq'),
);
$items['sql-sync'] = array(
'description' => 'Copy and import source database to target database. Transfers via rsync.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'drush dependencies' => array('core'), // core-rsync.
'examples' => array(
'drush sql-sync @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-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.',
'source-target' => 'Oy. A key within the --target_database identifying a particular server in the database group.',
'target-db-url' => '',
'target-remote-port' => '',
'target-remote-host' => '',
'target-dump' => '',
'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.',
'target-target' => 'Oy. A key within the --target_database identifying a particular server in the database group.',
'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.',
'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.',
'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync. Optional.',
),
'sub-options' => array(
'sanitize' => array(
'sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".',
'sanitize-email' => 'The username for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %login. Default is "user+%uid@localhost".',
'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations',
),
),
'topics' => array('docs-aliases', 'docs-policy'),
);
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'),
);
$items['sql-sanitize'] = array(
'description' => "Run sanitization operations on the current database.",
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
'hidden' => TRUE,
'options' => array(
'sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".',
'sanitize-email' => 'The username for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %login. Default is "user+%uid@localhost".',
),
'aliases' => array('sqlsan'),
);
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 = _drush_sql_get_all_db_specs();
}
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';
break;
case 'pgsql':
$command = 'psql';
break;
case 'sqlite':
$command = 'sqlite3';
break;
}
$command .= _drush_sql_get_credentials($db_spec);
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() {
list($exec, $file) = drush_sql_dump();
// Avoid the php memory of the $output array in drush_shell_exec().
if (!$return = drush_op_system($exec)) {
if ($file) {
drush_log(dt('Database dump saved to !path', array('!path' => $file)), 'success');
}
}
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/pg_dump/sqlite statement.
*
* @param db_spec
* For D5/D6, a $db_url. For D7, a target in the default DB connection.
* @return array
* An array with items.
* 1. A mysqldump/pg_dump/sqlite statement that is ready for executing.
* 2. The filepath where the dump will be saved.
*/
function drush_sql_dump($db_spec = NULL) {
return drush_sql_build_dump_command(drush_sql_get_table_selection(), $db_spec);
}
/**
* Build a mysqldump/pg_dump/sqlite statement.
*
* @param array $table_selection
* Supported keys: 'skip', 'structure', 'tables'.
* @param db_spec
* For D5/D6, a $db_url. For D7, a target in the default DB connection.
* @return array
* An array with items.
* 1. A mysqldump/pg_dump/sqlite statement that is ready for executing.
* 2. The filepath where the dump will be saved.
*/
function drush_sql_build_dump_command($table_selection, $db_spec = NULL) {
$skip_tables = $table_selection['skip'];
$structure_tables = $table_selection['structure'];
$tables = $table_selection['tables'];
$ignores = array();
$skip_tables = array_merge($structure_tables, $skip_tables);
$data_only = drush_get_option('data-only');
// The ordered-dump option is only supported by MySQL for now.
// @todo add documention once a hook for drush_get_option_help() is available.
// @see drush_get_option_help() in drush.inc
$ordered_dump = drush_get_option('ordered-dump');
if (is_null($db_spec)) {
$db_spec = _drush_sql_get_db_spec();
}
$database = $db_spec['database'];
// Get the setting of --result-file. If the user
// has set $options['result-file'] = TRUE, then we
// will generate an SQL dump file in the same backup
// directory that pm-updatecode uses.
$file = NULL;
if ($file = drush_get_option('result-file', FALSE)) {
if ($file === TRUE) {
// User did not pass a specific value for --result-file. Make one.
drush_include_engine('version_control', 'backup');
$backup = new drush_pm_version_control_backup();
$backup_dir = $backup->prepare_backup_dir($db_spec['database']);
if (empty($backup_dir)) {
$backup_dir = "/tmp";
}
$file = $backup_dir . '/@DATABASE_@DATE.sql';
}
$file = str_replace(array('@DATABASE', '@DATE'), array($database, gmdate('Ymd_his')), $file);
}
switch (_drush_sql_get_scheme($db_spec)) {
case 'mysql':
$exec = 'mysqldump';
if ($file) {
$exec .= ' --result-file '. $file;
}
// mysqldump wants 'databasename' instead of 'database=databasename' for no good reason.
$extra = ' --single-transaction --opt -Q' . str_replace('--database=', ' ', _drush_sql_get_credentials($db_spec));
if (isset($data_only)) {
$extra .= ' --no-create-info';
}
if (isset($ordered_dump)) {
$extra .= ' --skip-extended-insert --order-by-primary';
}
$exec .= $extra;
if (!empty($tables)) {
$exec .= ' ' . implode(' ', $tables);
}
else {
// Append the ignore-table options.
foreach ($skip_tables as $table) {
$ignores[] = "--ignore-table=$database.$table";
}
$exec .= ' '. implode(' ', $ignores);
// Run mysqldump again and append output if we need some structure only tables.
if (!empty($structure_tables)) {
$exec .= "; mysqldump --no-data $extra " . implode(' ', $structure_tables);
if ($file) {
$exec .= " >> $file";
}
}
}
break;
case 'pgsql':
$create_db = drush_get_option('create-db');
$exec = 'pg_dump ';
if ($file) {
$exec .= ' --file '. $file;
}
// Unlike psql, pg_dump does not take a '--dbname=' before the database name.
$extra = str_replace('--dbname=', ' ', _drush_sql_get_credentials($db_spec));
if (isset($data_only)) {
$extra .= ' --data-only';
}
$exec .= $extra;
$exec .= (!isset($create_db) && !isset($data_only) ? ' --clean' : '');
if (!empty($tables)) {
foreach ($tables as $table) {
$exec .= " --table=$table";
}
}
else {
foreach ($skip_tables as $table) {
$ignores[] = "--exclude-table=$table";
}
$exec .= ' '. implode(' ', $ignores);
// Run pg_dump again and append output if we need some structure only tables.
if (!empty($structure_tables)) {
$schemaonlies = array();
foreach ($structure_tables as $table) {
$schemaonlies[] = "--table=$table";
}
$exec .= "; pg_dump --schema-only " . implode(' ', $schemaonlies) . $extra;
if ($file) {
$exec .= " >> $file";
}
}
}
break;
case 'sqlite':
// Dumping is usually not necessary in SQLite, since all database data
// is stored in a single file on the filesystem which can be copied just
// like any other file. But it still has a use in migration purposes and
// building human-readable diffs and such, so let's do it anyway.
$exec = _drush_sql_connect();
// SQLite's dump command doesn't support many of the features of its
// Postgres or MySQL equivalents. We may be able to fake some in the
// future, but for now, let's just support simple dumps.
$exec .= ' ".dump"';
if ($file = drush_get_option('result-file')) {
$exec .= ' > '. $file;
}
break;
}
if (drush_get_option('gzip')) {
if ($file) {
// Gzip the result-file
$exec .= "; gzip $file";
$file .= '.gz';
}
else {
// gzip via pipe since user has not specified a file.
$exec .= "| gzip";
}
}
return array($exec, $file);
}
/**
* Consult the specified options and return the list of tables
* specified.
*
* @param option_name
* The option name to check: skip-tables, structure-tables
* or tables. This 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('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) {
foreach(explode(',',$prefix_list) as $prefix) {
$key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context);
foreach(explode(',', $key_list) as $key) {
$all_tables = drush_get_option($option_name, array());
if (array_key_exists($key, $all_tables)) {
return $all_tables[$key];
}
if ($option_name != 'tables') {
$all_tables = drush_get_option('tables', array());
if (array_key_exists($key, $all_tables)) {
return $all_tables[$key];
}
}
}
$table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context);
if (isset($table_list)) {
return empty($table_list) ? array() : explode(',', $table_list);
}
}
}
return array();
}
/**
* Command callback. Executes the given SQL query on the Drupal database.
*/
function drush_sql_query($query) {
$filename = drush_get_option('file', NULL);
return _drush_sql_query($query, NULL, $filename);
}
/*
* Execute a SQL query.
*
* @param string $query
* The SQL to be executed. Should be NULL if $file is provided.
* @param array $db_spec
* A database target.
* @param string $filename
* A path to a file containing the SQL to be executed.
*/
function _drush_sql_query($query, $db_spec = NULL, $filename = NULL) {
$scheme = _drush_sql_get_scheme($db_spec);
// Inject table prefixes as needed.
if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_DATABASE)) {
if ($filename) {
$query = file_get_contents($filename);
}
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 = drush_sql_show_tables_pgsql();
}
// Save $query to a tmp file if needed. We will redirect it in.
if (!$filename) {
$filename = drush_save_data_to_temp_file($query);
}
$exec = drush_sql_build_exec($db_spec, $filename);
if ($output_file = drush_get_option('result-file')) {
$exec .= ' > '. drush_escapeshellarg($output_file);
}
// In --simulate mode, drush_op will show the call to mysql or psql,
// but the sql query itself is stored in a temp file and not displayed.
// We will therefore show the query explicitly in the interest of full disclosure.
if (drush_get_context('DRUSH_SIMULATE')) {
drush_print('sql-query: ' . $query);
}
$return = drush_op_system($exec) == 0;
return $return;
}
function drush_sql_drop() {
if (!drush_confirm(dt('Do you really want to drop all tables?'))) {
return drush_user_abort();
}
_drush_sql_drop();
}
// n.b. site-install uses _drush_sql_drop as a fallback technique if
// drop database; create database fails. If _drush_sql_drop
// is rewritten to also use that technique, it should maintain
// the drop tables code here as a fallback.
function _drush_sql_drop($db_spec = NULL) {
// TODO: integrate with _drush_sql_get_table_list?
$scheme = _drush_sql_get_scheme($db_spec);
switch ($scheme) {
case 'pgsql':
$query = drush_sql_show_tables_pgsql();
break;
case 'sqlite':
$query = '.tables';
break;
default:
$query = 'SHOW TABLES;';
}
$filename = drush_save_data_to_temp_file($query);
$exec = drush_sql_build_exec($db_spec, $filename);
// Actually run this prep query no matter if in SIMULATE.
$old = drush_get_context('DRUSH_SIMULATE');
drush_set_context('DRUSH_SIMULATE', FALSE);
drush_shell_exec($exec);
drush_set_context('DRUSH_SIMULATE', $old);
if ($tables = drush_shell_exec_output()) {
if ($scheme === 'sqlite') {
// SQLite's '.tables' command always outputs the table names in a column
// format, like this:
// table_alpha table_charlie table_echo
// table_bravo table_delta table_foxtrot
// …and there doesn't seem to be a way to fix that. So we need to do some
// clean-up.
// Since we're already doing iteration here, might as well build the SQL
// too, since SQLite only wants one table per DROP TABLE command (so we have
// to do "DROP TABLE foo; DROP TABLE bar;" instead of
// "DROP TABLE foo, bar;").
$sql = '';
foreach ($tables as $line) {
preg_match_all('/[^\s]+/', $line, $matches);
if (!empty($matches[0])) {
foreach ($matches[0] as $match) {
$sql .= "DROP TABLE {$match};";
}
}
}
// We can't use drush_op('db_query', $sql) because it will only perform one
// SQL command and we're technically performing several.
$exec = _drush_sql_connect($db_spec);
$exec .= " '{$sql}'";
return drush_op_system($exec) == 0;
}
else {
// Shift off the header of the column of data returned.
array_shift($tables);
$sql = 'DROP TABLE '. implode(', ', $tables);
return _drush_sql_query($sql, $db_spec);
}
}
else {
drush_log(dt('No tables to drop.'), 'ok');
}
return TRUE;
}
function drush_sql_cli() {
proc_close(proc_open(_drush_sql_connect(), array(0 => STDIN, 1 => STDOUT, 2 => STDERR), $pipes));
}
/**
* Command callback. Run's the sanitization operations on the current database.
*/
function drush_sql_sanitize() {
if (!drush_confirm(dt('Do you really want to sanitize the current database?'))) {
return drush_user_abort();
}
drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
drush_command_invoke_all('drush_sql_sync_sanitize', 'default');
$options = drush_get_context('post-sync-ops');
if (!empty($options)) {
if (!drush_get_context('DRUSH_SIMULATE')) {
$messages = _drush_sql_get_post_sync_messages();
if ($messages) {
drush_print();
drush_print($messages);
}
}
}
$sanitize_query = '';
foreach($options as $id => $data) {
$sanitize_query .= $data['query'] . " ";
}
if ($sanitize_query) {
if (!drush_get_context('DRUSH_SIMULATE')) {
drush_sql_query($sanitize_query);
}
else {
drush_print("Executing: $sanitize_query");
}
}
}
//////////////////////////////////////////////////////////////////////////////
// SQL SERVICE HELPERS
/**
* Get a database specification for the active DB connection. Honors the
* 'database' and 'target command' line options.
*
* @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_all_db_specs() {
switch (drush_drupal_major_version()) {
case 5:
case 6:
return drush_sitealias_convert_db_from_db_url($GLOBALS['db_url']);
default:
if (!isset($GLOBALS['databases'])) {
return NULL;
}
return $GLOBALS['databases'];
}
}
function _drush_sql_get_spec_from_options($prefix, $default_to_self = TRUE) {
$db_spec = NULL;
$databases = drush_get_option($prefix . 'databases');
if (isset($databases) && !empty($databases)) {
$database = drush_get_option($prefix . 'database', 'default');
$target = drush_get_option($prefix . 'target', 'default');
if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) {
$db_spec = $databases[$database][$target];
}
}
else {
$db_url = drush_get_option($prefix . 'db-url');
if (isset($db_url)) {
$db_spec = drush_convert_db_from_db_url($db_url);
}
elseif ($default_to_self) {
$db_spec = _drush_sql_get_db_spec();
}
}
if (isset($db_spec)) {
$remote_host = drush_get_option($prefix . 'remote-host');
if (!drush_is_local_host($remote_host)) {
$db_spec['remote-host'] = $remote_host;
$db_spec['port'] = drush_get_option($prefix . 'remote-port', $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, $prefix) {
// Use an entry in the db spec to indicate whether the dump
// file we use is temporary or not.
$db_spec['dump-is-temp'] = FALSE;
// 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 --{prefix}-dir option, then
// use the exact name provided.
$dump_file = drush_get_option($prefix . 'dump');
if (!isset($dump_file)) {
// If the user has set the --dump-dir option, then
// store persistant sql dump files there.
$dump_dir = drush_get_option(array($prefix . '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();
}
// Build an array of key-value pairs for the parameters.
$parameters = array();
switch (_drush_sql_get_scheme($db_spec)) {
case 'mysql':
// Some drush commands (e.g. site-install) want to connect to the
// server, but not the database. Connect to the built-in database.
$parameters['database'] = empty($db_spec['database']) ? 'information_schema' : $db_spec['database'];
// Host is required.
$parameters['host'] = $db_spec['host'];
// An empty port is invalid.
if (!empty($db_spec['port'])) {
$parameters['port'] = $db_spec['port'];
}
// User is required. Drupal calls it 'username'. MySQL calls it 'user'.
$parameters['user'] = $db_spec['username'];
// EMPTY password is not the same as NO password, and is valid.
if (isset($db_spec['password'])) {
$parameters['password'] = $db_spec['password'];
}
break;
case 'pgsql':
// Some drush commands (e.g. site-install) want to connect to the
// server, but not the database. Connect to the built-in database.
$parameters['dbname'] = empty($db_spec['database']) ? 'template1' : $db_spec['database'];
// Host and port are optional but have defaults.
$parameters['host'] = empty($db_spec['host']) ? 'localhost' : $db_spec['host'];
$parameters['port'] = empty($db_spec['port']) ? '5432' : $db_spec['port'];
// Username is required.
$parameters['username'] = $db_spec['username'];
// Don't set the password.
// @see http://drupal.org/node/438828
break;
case 'sqlite':
// SQLite doesn't do user management, instead relying on the filesystem
// for that. So the only info we really need is the path to the database
// file, and not as a "--key=value" parameter.
return ' ' . $db_spec['database'];
break;
}
// Turn each parameter into a valid parameter string.
$parameter_strings = array();
foreach ($parameters as $key => $value) {
// Only escape the values, not the keys or the rest of the string.
$value = escapeshellcmd($value);
$parameter_strings[] = "--$key=$value";
}
// Join the parameters and return.
return ' ' . implode(' ', $parameter_strings);
}
function _drush_sql_get_invalid_url_msg($db_spec = NULL) {
if (is_null($db_spec)) {
$db_spec = _drush_sql_get_db_spec();
}
switch (drush_drupal_major_version()) {
case 5:
case 6:
return dt('Unable to parse DB connection string');
default:
return dt('Unable to parse DB connection array');
}
}
/**
* Call from a pre-sql-sync hook to register an sql
* query to be executed in the post-sql-sync hook.
* @see drush_sql_pre_sql_sync() and @see drush_sql_post_sql_sync().
*
* @param $id
* String containing an identifier representing this
* operation. This id is not actually used at the
* moment, it is just used to fufill the contract
* of drush contexts.
* @param $message
* String with the confirmation message that describes
* to the user what the post-sync operation is going
* to do. This confirmation message is printed out
* just before the user is asked whether or not the
* sql-sync operation should be continued.
* @param $query
* String containing the sql query to execute. If no
* query is provided, then the confirmation message will
* be displayed to the user, but no action will be taken
* in the post-sync hook. This is useful for drush modules
* that wish to provide their own post-sync hooks to fix
* up the target database in other ways (e.g. through
* Drupal APIs).
*/
function drush_sql_register_post_sync_op($id, $message, $query = NULL) {
$options = drush_get_context('post-sync-ops');
$options[$id] = array('message' => $message, 'query' => $query);
drush_set_context('post-sync-ops', $options);
}
/**
* Builds a confirmation message for all post-sync operations.
*
* @return string
* All post-sync operation messages concatenated together.
*/
function _drush_sql_get_post_sync_messages() {
$messages = FALSE;
$options = drush_get_context('post-sync-ops');
if (!empty($options)) {
$messages = dt('The following post-sync operations will be done on the destination:') . "\n";
foreach($options as $id => $data) {
$messages .= " * " . $data['message'] . "\n";
}
}
return $messages;
}
// Convert mysql 'show tables;' query into something pgsql understands.
function drush_sql_show_tables_pgsql() {
return "select tablename from pg_tables where schemaname='public';";
}
/*
* Drop all tables or DROP+CREATE target database.
*
* return boolean
* TRUE or FALSE depending on success.
*/
function drush_sql_empty_db($db_spec = NULL) {
if (is_null($db_spec)) {
$db_spec = drush_sql_read_db_spec();
}
$sql = drush_sql_build_createdb_sql($db_spec, TRUE);
// Get credentials to connect to the server, but not the database which we
// are about to DROP. @see _drush_sql_get_credentials().
$create_db_spec = $db_spec;
unset($create_db_spec['database']);
$create_db_su = drush_sql_su($create_db_spec);
if (!_drush_sql_query($sql, $create_db_su)) {
// If we could not drop the database, try instead to drop all
// of the tables in the database (presuming it exists...).
// If we cannot do either operation, then fail with an error.
if (!_drush_sql_drop($db_spec)) {
return drush_set_error(dt('Could not drop and create database: @name', array('@name' => $db_name)));
}
}
return TRUE;
}
/**
* Return a db_spec based on supplied db_url/db_prefix options or an existing
* settings.php.
*/
function drush_sql_read_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;
}
}
/*
* Build DB connection array with superuser credentials if provided.
*/
function drush_sql_su($db_spec) {
$create_db_target = $db_spec;
$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;
}
return $create_db_target;
}
/*
* Build a SQL string for dropping and creating a database.
*
* @param array $db_spec
* A database specification array.
*
* @param boolean $quoted
* Quote the database name. Mysql uses backticks to quote which can cause problems
* in a Windows shell. Set TRUE of the CREATE is not running on the bash command line.
*/
function drush_sql_build_createdb_sql($db_spec, $quoted = FALSE) {
switch (_drush_sql_get_scheme($db_spec)) {
case 'mysql':
$dbname = $quoted ? '`' . $db_spec['database'] . '`' : $db_spec['database'];
$sql[] = sprintf('DROP DATABASE IF EXISTS %s; ', $dbname);
$sql[] = sprintf('CREATE DATABASE %s /*!40100 DEFAULT CHARACTER SET utf8 */;', $dbname);
$sql[] = sprintf('GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@\'%s\'', $dbname, $db_spec['username'], $db_spec['host']);
$sql[] = sprintf("IDENTIFIED BY '%s';", $db_spec['password']);
$sql[] = 'FLUSH PRIVILEGES;';
break;
case 'pgsql':
$dbname = $quoted ? '"' . $db_spec['database'] . '"' : $db_spec['database'];
$sql[] = sprintf('drop database if exists %s;', $dbname);
$sql[] = sprintf("create database %s ENCODING 'UTF8';", $dbname);
break;
}
return implode(' ', $sql);
}
function drush_sql_build_exec($db_spec, $filepath) {
$scheme = _drush_sql_get_scheme($db_spec);
switch ($scheme) {
case 'mysql':
$exec = 'mysql';
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= ' ' . drush_get_option('extra');
$exec .= " < $filepath";
break;
case 'pgsql':
$exec = 'psql -q ';
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= ' ' . (drush_get_option('extra') ? drush_get_option('extra') : "--no-align --field-separator='\t' --pset footer=off");
$exec .= " --file $filepath";
break;
case 'sqlite':
$exec = 'sqlite3';
$exec .= ' ' . drush_get_option('extra');
$exec .= _drush_sql_get_credentials($db_spec);
$exec .= " < $filepath";
break;
}
return $exec;
}

View File

@@ -0,0 +1,488 @@
<?php
require_once DRUSH_BASE_PATH . '/commands/core/rsync.core.inc';
/**
* Sql sync init function. Bootstrap either the source or the
* destination site. At least one of the sites
* must be local for this to work; if both sites are remote,
* then it clearly will not be possible to bootstrap to
* either of them. If both are local, the source site is preferred.
*/
function drush_sql_sync_init($source = NULL, $destination = NULL) {
// 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);
// Bootstrap to the source sites being sync'ed if it is local.
// This allows modules enabled in the site to participate in the
// sql-sync hook functions (e.g. to add sanitization operations, etc.).
// If the source is remote and the destination is local, then we
// will determine the sanitization operations after the database
// has been copied.
if (!drush_get_option('deferred-sanitization', FALSE) && drush_get_option(array('sanitize', 'destination-sanitize'), FALSE)) {
$bootstrapped = drush_bootstrap_max_to_sitealias($source_settings);
if ($bootstrapped) {
drush_command_invoke_all('drush_sql_sync_sanitize', $source);
}
else {
drush_set_option('deferred-sanitization', TRUE);
}
}
// By default, sql-sync will do an ordered dump.
// Set --no-ordered-dump to override.
if (!drush_get_option('no-ordered-dump', FALSE)) {
drush_set_option('ordered-dump', TRUE);
}
return TRUE;
}
/**
* Sql sync sanitization function. This hook function will sanitize usernames and
* passwords in the user table when the --sanitize option is used. It is
* also an example of how to write a database sanitizer for sql sync.
*
* To write your own sync hook function, define mymodule_drush_sql_sync_sanitize()
* and follow the form of this function to add your own database
* sanitization operations via the register post-sync op function;
* @see drush_sql_register_post_sync_op(). This is the only thing that the
* sync hook function needs to do; sql-sync takes care of the rest.
*
* The function below has a lot of logic to process user preferences and
* generate the correct SQL regardless of whether Postgres, Mysql,
* Drupal 6 or Drupal 7 is in use. A simpler sanitize function that
* always used default values and only worked with Drupal 6 + mysql
* appears in the drush.api.php. @see hook_drush_sql_sync_sanitize().
*/
function sql_drush_sql_sync_sanitize($site) {
$site_settings = drush_sitealias_get_record($site);
$user_table_updates = array();
$message_list = array();
// Sanitize passwords.
$newpassword = drush_get_option(array('sanitize-password', 'destination-sanitize-password'), 'password');
if ($newpassword != 'no') {
$major_version = drush_drupal_major_version();
$pw_op = "";
// In Drupal 6, passwords are hashed via the MD5 algorithm.
if ($major_version == 6) {
$pw_op = "MD5('$newpassword')";
}
// In Drupal 7, passwords are hashed via a more complex algorithm,
// available via the user_hash_password function.
elseif ($major_version >= 7) {
include_once DRUPAL_ROOT . '/includes/password.inc';
include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
$hash = user_hash_password($newpassword);
$pw_op = "'$hash'";
}
if (!empty($pw_op)) {
$user_table_updates[] = "pass = $pw_op";
$message_list[] = "passwords";
}
}
// Sanitize email addresses.
$newemail = drush_get_option(array('sanitize-email', 'destination-sanitize-email'), 'user+%uid@localhost');
if ($newemail != 'no') {
if (strpos($newemail, '%') !== FALSE) {
// We need a different sanitization query for Postgres and Mysql.
$db_driver = $site_settings['databases']['default']['default']['driver'];
if ($db_driver == 'pgsql') {
$email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '");
$newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
}
else {
$email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '");
$newmail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')";
}
}
$user_table_updates[] = "mail = $newmail";
$message_list[] = 'email addresses';
}
if (!empty($user_table_updates)) {
$sanitize_query = "update users set " . implode(', ', $user_table_updates) . " where uid > 0;";
drush_sql_register_post_sync_op('user-email', dt('Reset !message in user table', array('!message' => implode(' and ', $message_list))), $sanitize_query);
}
}
function drush_sql_sync($source = NULL, $destination = NULL) {
$source_settings = drush_sitealias_get_record($source);
$destination_settings = drush_sitealias_get_record($destination);
// 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_sql_dump_file($source_db_url, 'source-');
$target_dump = drush_sql_dump_file($target_db_url, 'target-');
$use_temp_files = drush_get_option('temp');
// Only use one dump file if both the source and the target are on the local machine
if (!isset($source_db_url['remote-host']) && !isset($target_db_url['remote-host'])) {
if ((!$target_db_url['dump-is-temp']) && ($source_db_url['dump-is-temp'])) {
$source_dump = $target_dump;
$source_db_url['dump-is-temp'] = FALSE;
}
else {
$target_dump = $source_dump;
$target_db_url['dump-is-temp'] = $source_db_url['dump-is-temp'];
}
$local_file = $source_dump;
}
else {
// If one of the systems is remote, then set the --remove-source-files
// rsync option if the source dump file is temporary. This will get
// rsync to clean up after us automatically; useful if the source is remote.
if ($source_db_url['dump-is-temp']) {
$source_rsync_options['remove-source-files'] = TRUE;
}
// Set $local_file to whichever side of the operation is local, or make
// a temporary file if both source and target are remote.
if (!isset($source_db_url['remote-host'])) {
$local_file = $source_dump;
}
elseif (!isset($target_db_url['remote-host'])) {
$local_file = $target_dump;
}
else {
$local_file = drush_tempnam($source_db_url['database'] . ($source_db_url['database'] == $target_db_url['database'] ? '' : '-to-' . $target_db_url['database']) . '.sql.');
}
}
// If source is remote, then use ssh to dump the database and then rsync to local machine
// If source is local, call drush_sql_dump to dump the database to local machine
// In either case, the '--no-dump' option will cause the sql-dump step to be skipped, and
// we will import from the existing local file (first using rsync to fetch it if it does not exist)
//
// No dump affects both local and remote sql-dumps; it prevents drush sql-sync
// from calling sql-dump when the local cache file is newer than the cache threshhold
// No sync affects the remote sql-dump; it will prevent drush sql-sync from
// rsyncing the local sql-dump file with the remote sql-dump file.
$no_sync = drush_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 ((time() - filemtime($local_file)) < ($cache * 60 * 60)) {
drush_log(dt('Modification time of local dump file !file is less than !cache hours old. Use the --no-cache option to force a refresh.', array('!file' => $local_file, '!cache' => $cache)), 'warning');
$no_dump = TRUE;
$no_sync = TRUE;
}
else {
drush_log(dt('Local sql cache file exists but is greater than !cache hours old.', array('!cache' => $cache)));
}
}
else {
drush_log('Local sql cache file does not exist.');
}
}
$table_selection = array();
if (!isset($no_dump)) {
$table_selection = drush_sql_get_table_selection();
}
// Prompt for confirmation. This is destructive.
if (!drush_get_context('DRUSH_SIMULATE')) {
// Check to see if we are using a temporary file in a situation
// where the user did not specify "--temp".
if (($source_db_url['dump-is-temp'] || $target_db_url['dump-is-temp']) && (!isset($use_temp_files)) && (isset($source_db_url['remote-host']) || isset($target_db_url['remote-host']))) {
drush_print(dt('WARNING: Using temporary files to store and transfer sql-dump. It is recommended that you specify --source-dump and --target-dump options on the command line, or set \'%dump\' or \'%dump-dir\' in the path-aliases section of your site alias records. This facilitates fast file transfer via rsync.'));
}
if (array_key_exists('tables', $table_selection) && (count($table_selection['tables']) > 0)) {
drush_print();
drush_print(dt(' Only the following tables will be transferred: !list', array('!list' => implode(',', $table_selection['tables']))));
}
elseif (!empty($table_selection)) {
$skip_tables_list = implode(',', $table_selection['skip'] + $table_selection['structure']);
if(!empty($skip_tables_list)) {
drush_print();
drush_print(dt(' The following tables will be skipped: !list', array('!list' => $skip_tables_list)));
}
}
// If there are multiple destinations, then
// prompt once here and suppress the warning message
// and the normal confirmation below.
if (array_key_exists('site-list', $destination_settings)) {
drush_print();
drush_print(dt('You are about to sync the database from !source, overwriting all of the following targets:', array('!source' => $source)));
foreach ($destination_settings['site-list'] as $one_destination) {
drush_print(dt(' !target', array('!target' => $one_destination)));
}
}
else {
drush_print();
$txt_source = (isset($source_db_url['remote-host']) ? $source_db_url['remote-host'] . '/' : '') . $source_db_url['database'];
$txt_destination = (isset($target_db_url['remote-host']) ? $target_db_url['remote-host'] . '/' : '') . $target_db_url['database'];
drush_print(dt("You will destroy data from !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination)));
}
// If any sanitization operations are to be done, then get the
// sanitization messages and print them as part of the confirmation.
// If --sanitize was specified but there were no sanitize messages,
// then warn that sanitization operations will be accumulated and
// processed after the sync completes.
$messages = _drush_sql_get_post_sync_messages();
if ($messages) {
drush_print();
drush_print($messages);
}
else if (drush_get_option('deferred-sanitization', FALSE) && !drush_get_option('confirm-sanitizations', FALSE)) {
drush_print();
drush_print("WARNING: --sanitize was specified, but deferred (e.g. the source site is remote). The sanitization operations will be determined after the database is copied to the local system and will be run without further confirmation. Run with --confirm-sanitizations to force confirmation after the sync.");
}
// TODO: actually make the backup if desired.
drush_print();
drush_print(dt("You might want to make a backup first, using the sql-dump command.\n"));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
if (isset($source_db_url['remote-host'])) {
$source_remote_user = drush_get_option('source-remote-user');
$source_at = '';
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 (!$source_db_url['dump-is-temp']) {
$source_intermediate = $source_dump . '-' . date("U");
$mv_intermediate = '; mv -f ' . $source_intermediate . ' ' . $source_dump;
}
drush_set_option('result-file', $source_intermediate);
list($dump_exec, $dump_file) = drush_sql_build_dump_command($table_selection, $source_db_url);
$dump_exec .= $mv_intermediate;
if (isset($cache) && !$source_db_url['dump-is-temp']) {
// Inject some bash commands to remotely test the modification date of the target file
// if the cache option is set.
$dump_exec = 'if [ ! -s ' . $source_dump . ' ] || [ $((`date "+%s"`-`stat --format="%Y" ' . $source_dump . '`)) -gt ' . ($cache * 60 * 60) . ' ] ; then ' . $dump_exec . '; fi';
}
$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);
list($dump_exec, $dump_file) = 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).
if (!drush_core_call_rsync($source_remote_user . $source_at . $source_db_url['remote-host'] . ':' . $source_dump, $local_file, $source_rsync_options)) {
return FALSE;
}
}
// We will handle lists of destination sites differently from
// single source-to-destination syncs.
if (array_key_exists('site-list', $destination_settings)) {
// Insure that we will not dump the source sql database
// repeatedly, but will instead re-use it each time through
// the redispatch loop.
drush_set_option('no-dump', TRUE);
drush_set_option('no-sync', TRUE);
drush_set_option('source-dump', $source_dump);
// Call sql-sync for each destination to push the $source_dump
// to each target in turn.
foreach ($destination_settings['site-list'] as $one_destination) {
drush_do_command_redispatch('sql-sync', array($source, $one_destination));
}
}
else {
// Prior to database import, we will generate a "create database" command
// if the '--create-db' option was specified. Note that typically the
// web server user will not have permissions to create a database; to specify
// a different user to use with the create db command, the '--db-su' option
// may be used.
// Under postgres, "alter role username with createdb;" will give create database
// permissions to the specified user if said user was not created with this right.
$pre_import_commands = '';
$create_db = drush_get_option('create-db');
if (isset($create_db)) {
$create_db_su = drush_sql_su($target_db_url);
$db_su_connect = _drush_sql_connect($create_db_su);
$pre_import_sql = drush_sql_build_createdb_sql($target_db_url);
$pre_import_commands = sprintf('echo "%s" | %s; ', $pre_import_sql, $db_su_connect);
}
// Generate the import command
$import_command = _drush_sql_connect($target_db_url);
switch (_drush_sql_get_scheme($target_db_url)) {
case 'mysql':
$import_command .= ' --silent';
break;
case 'pgsql':
$import_command .= ' -q';
break;
}
// If destination is remote, then use rsync to push the database, then use ssh to import the database
// If destination is local, then just import the database locally
if (isset($target_db_url['remote-host'])) {
$target_remote_user = drush_get_option('target-remote-user');
$target_at = '';
if (isset($target_remote_user)) {
$target_at ='@';
$target_remote_pass = drush_get_option('target-remote-pass') ? ':' . drush_get_option('target-remote-pass') : '';
}
if (!drush_core_call_rsync($local_file, $target_remote_user . $target_at . $target_db_url['remote-host'] . ':' . $target_dump, $target_rsync_options)) {
return FALSE;
}
$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_db_url['dump-is-temp']) {
$import_exec .= '; rm -f ' . escapeshellarg($target_dump);
}
}
else {
$import_exec = $pre_import_commands . $import_command . ' < ' . $local_file;
}
drush_op_system($import_exec);
// After the database is imported into the destination, we
// will check and see if we did not collect sanitization
// operations in drush_sql_sync_init (i.e. because the source
// site was remote), and if the destination site is local,
// then we will call the sanitization hooks now.
// This presumes an important precondition, that the code
// files were sync'ed before the database was sync'ed.
if (drush_get_option('deferred-sanitization', FALSE) && (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_SITE) == FALSE)) {
$bootstrapped = drush_bootstrap_max_to_sitealias($destination_settings);
if ($bootstrapped) {
drush_command_invoke_all('drush_sql_sync_sanitize', $destination);
}
}
}
}
}
/**
* Apply all post-sync operations that were registered in any pre-sync hook.
* Follow the pattern of this function to make your own post-sync hook.
* If changing the database, be sure to also include a pre-sync hook to
* notify the user of the change that will be made. @see drush_sql_pre_sql_sync().
*/
function drush_sql_post_sql_sync($source = NULL, $destination = NULL) {
$options = drush_get_context('post-sync-ops');
if (!empty($options)) {
// If 'deferred-sanitization' is set, then we collected the
// sanitization operations -after- the database sync, which
// means they were not confirmed up-front. We will show the
// operations here, but we will not offer an opportunity to
// confirm unless --confirm-sanitizations is specified.
if (drush_get_option('deferred-sanitization', FALSE) || drush_get_option('confirm-sanitizations', FALSE)) {
if (!drush_get_context('DRUSH_SIMULATE')) {
$messages = _drush_sql_get_post_sync_messages();
if ($messages) {
drush_print();
drush_print($messages);
if (drush_get_option('confirm-sanitizations', FALSE)) {
if (!drush_confirm(dt('Do you really want to sanitize?'))) {
// Do not abort or return FALSE; that would trigger a rollback.
// Just skip the sanitizations and signal that all is ok.
drush_log(dt('Sanitizations skipped.'), 'ok');
return TRUE;
}
}
}
}
}
$destination_settings = drush_sitealias_get_record($destination);
$sanitize_query = '';
foreach($options as $id => $data) {
$sanitize_query .= $data['query'] . " ";
}
if ($sanitize_query) {
if (!drush_get_context('DRUSH_SIMULATE')) {
$result = drush_invoke_sitealias_args($destination_settings, "sql-query", array($sanitize_query));
}
else {
drush_print("Executing on $destination: $sanitize_query");
}
}
}
}

View File

@@ -0,0 +1,572 @@
<?php
// $Id:
/**
* @file Drush User Management commands
*/
function user_drush_help($section) {
switch ($section) {
case 'meta:user:title':
return dt('User commands');
case 'meta:user:summary':
return dt('Add, modify and delete users.');
}
}
/**
* Implementation of hook_drush_command().
*/
function user_drush_command() {
$items['user-information'] = array(
'callback' => 'drush_user_information',
'description' => 'Print information about the specified user(s).',
'aliases' => array('uinf'),
'examples' => array(
'drush user-information 2,3,someguy,somegal,billgates@microsoft.com' =>
'Display information about any users with uids, names, or mail addresses matching the strings between commas.',
),
'arguments' => array(
'users' => 'A comma delimited list of uids, user names, or email addresses.',
),
'options' => array(
'full' => 'show extended information about the user',
'short' => 'show basic information about the user (this is the default)',
),
);
$items['user-block'] = array(
'callback' => 'drush_user_block',
'description' => 'Block the specified user(s).',
'aliases' => array('ublk'),
'arguments' => array(
'users' => 'A comma delimited list of uids, user names, or email addresses.',
),
'examples' => array(
'drush user-block 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
'Block the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
),
'options' => array(
'uid' => 'A comma delimited list of uids to block',
'name' => 'A comma delimited list of user names to block',
'mail' => 'A comma delimited list of user mail addresses to block',
),
);
$items['user-unblock'] = array(
'callback' => 'drush_user_unblock',
'description' => 'Unblock the specified user(s).',
'aliases' => array('uublk'),
'arguments' => array(
'users' => 'A comma delimited list of uids, user names, or email addresses.',
),
'examples' => array(
'drush user-unblock 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
'Unblock the users with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
),
'options' => array(
'uid' => 'A comma delimited list of uids to unblock',
'name' => 'A comma delimited list of user names to unblock',
'mail' => 'A comma delimited list of user mail addresses to unblock',
),
);
$items['user-add-role'] = array(
'callback' => 'drush_user_add_role',
'description' => 'Add a role to the specified user accounts.',
'aliases' => array('urol'),
'arguments' => array(
'role' => 'The name of the role to add',
'users' => '(optional) A comma delimited list of uids, user names, or email addresses.',
),
'examples' => array(
'drush user-add-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
'Add the "power user" role to the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
),
'options' => array(
'uid' => 'A comma delimited list of uids',
'name' => 'A comma delimited list of user names',
'mail' => 'A comma delimited list of user mail addresses',
),
);
$items['user-remove-role'] = array(
'callback' => 'drush_user_remove_role',
'description' => 'Remove a role from the specified user accounts.',
'aliases' => array('urrol'),
'arguments' => array(
'role' => 'The name of the role to remove',
'users' => '(optional) A comma delimited list of uids, user names, or email addresses.',
),
'examples' => array(
'drush user-remove-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
'Remove the "power user" role from the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
),
'options' => array(
'uid' => 'A comma delimited list of uids',
'name' => 'A comma delimited list of user names',
'mail' => 'A comma delimited list of user mail addresses',
),
);
$items['user-create'] = array(
'callback' => 'drush_user_create',
'description' => 'Create a user account with the specified name.',
'aliases' => array('ucrt'),
'arguments' => array(
'name' => 'The name of the account to add'
),
'examples' => array(
'drush user-create newuser --mail="person@example.com" --password="letmein"' =>
'Create a new user account with the name newuser, the email address person@example.com, and the password letmein',
),
'options' => array(
'password' => 'The password for the new account',
'mail' => 'The email address for the new account',
),
);
$items['user-cancel'] = array(
'callback' => 'drush_user_cancel',
'description' => 'Cancel a user account with the specified name.',
'aliases' => array('ucan'),
'arguments' => array(
'name' => 'The name of the account to cancel',
),
'examples' => array(
'drush user-cancel username' =>
'Cancel the user account with the name username and anonymize all content created by that user.',
),
);
$items['user-password'] = array(
'callback' => 'drush_user_password',
'description' => '(Re)Set the password for the user account with the specified name.',
'aliases' => array('upwd'),
'arguments' => array(
'name' => 'The name of the account to modify'
),
'options' => array(
'password' => '(required) The new password for the account',
),
'examples' => array(
'drush user-password someuser --password="gr3@tP@$s"' =>
'Set the password for the username someuser to gr3@tP@$s.',
),
);
$items['user-login'] = array(
'callback' => 'drush_user_login',
'description' => 'Display a one time login link for the given user account (defaults to uid 1).',
'aliases' => array('uli'),
'arguments' => array(
'name' => 'The name of the account to log in as. Leave it empty to log in as uid 1.'
),
'examples' => array(
'drush user-login ryan' => 'Displays a one-time login link for the user ryan.',
'open `drush user-login ryan`' => 'Open web browser and login as user ryan.',
),
);
// Drupal 7 only options.
if (drush_drupal_major_version() >= 7) {
$items['user-cancel']['options'] = array(
'delete-content' => 'Delete all content created by the user',
);
$items['user-cancel']['examples']['drush user-cancel --delete-content username'] =
'Cancel the user account with the name username and delete all content created by that user.';
}
return $items;
}
// Implementation of hook_drush_init().
function user_drush_init() {
$command_info = drush_get_command();
$command = $command_info['command'];
$needs_parse_args = array('user-block', 'user-unblock', 'user-add-role', 'user-remove-role');
if (in_array($command, $needs_parse_args)) {
// parse args and call drush_set_option for --uids
$users = array();
foreach (array('uid', 'name', 'mail' ) as $user_attr) {
if ($arg = drush_get_option($user_attr)) {
foreach(explode(',', $arg) as $search) {
$uid_query = FALSE;
switch ($user_attr) {
case 'uid':
if (drush_drupal_major_version() >= 7) {
$uid_query = db_query("SELECT uid FROM {users} WHERE uid = :uid", array(':uid' => $search));
}
else {
$uid_query = db_query("SELECT uid FROM {users} WHERE uid = %d", $search);
}
break;
case 'name':
if (drush_drupal_major_version() >= 7) {
$uid_query = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $search));
}
else {
$uid_query = db_query("SELECT uid FROM {users} WHERE name = '%s'", $search);
}
break;
case 'mail':
if (drush_drupal_major_version() >= 7) {
$uid_query = db_query("SELECT uid FROM {users} WHERE mail = :mail", array(':mail' => $search));
}
else {
$uid_query = db_query("SELECT uid FROM {users} WHERE mail = '%s'", $search);
}
break;
}
if ($uid_query !== FALSE) {
if ($uid = drush_db_result($uid_query)) {
$users[] = $uid;
}
else {
drush_set_error("Could not find a uid for $user_attr = $search");
}
}
}
}
}
if (!empty($users)) {
drush_set_option('uids', $users);
}
}
}
/**
* Prints information about the specified user(s).
*/
function drush_user_information($users) {
$users = explode(',', $users);
foreach($users as $user) {
$uid = _drush_user_get_uid($user);
if ($uid !== FALSE) {
_drush_user_print_info($uid);
}
}
}
/**
* Block the specified user(s).
*/
function drush_user_block($users = '') {
$uids = drush_get_option('uids');
if ($users !== '') {
$users = explode(',', $users);
foreach($users as $user) {
$uid = _drush_user_get_uid($user);
if ($uid !== FALSE) {
$uids[] = $uid;
}
}
}
if (!empty($uids)) {
drush_op('user_user_operations_block', $uids);
}
else {
return drush_set_error("Could not find any valid uids!");
}
}
/**
* Unblock the specified user(s).
*/
function drush_user_unblock($users = '') {
$uids = drush_get_option('uids');
if ($users !== '') {
$users = explode(',', $users);
foreach($users as $user) {
$uid = _drush_user_get_uid($user);
if ($uid !== FALSE) {
$uids[] = $uid;
}
}
}
if (!empty($uids)) {
drush_op('user_user_operations_unblock', $uids);
}
else {
return drush_set_error("Could not find any valid uids!");
}
}
/**
* Add a role to the specified user accounts.
*/
function drush_user_add_role($role, $users = '') {
$uids = drush_get_option('uids');
if ($users !== '') {
$users = explode(',', $users);
foreach($users as $user) {
$uid = _drush_user_get_uid($user);
if ($uid !== FALSE) {
$uids[] = $uid;
}
}
}
if (drush_drupal_major_version() >= 7) {
$rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role));
}
else {
$rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role);
}
if (!empty($uids)) {
if ($rid = drush_db_result($rid_query)) {
drush_op('user_multiple_role_edit', $uids, 'add_role', $rid);
foreach($uids as $uid) {
drush_log(dt("Added the %role role to uid %uid", array('%role' => $role, '%uid' => $uid)), 'success');
}
}
else {
return drush_set_error("There is no role named: \"$role\"!");
}
}
else {
return drush_set_error("Could not find any valid uids!");
}
}
/**
* Remove a role from the specified user accounts.
*/
function drush_user_remove_role($role, $users = '') {
$uids = drush_get_option('uids');
if ($users !== '') {
$users = explode(',', $users);
foreach($users as $user) {
$uid = _drush_user_get_uid($user);
if ($uid !== FALSE) {
$uids[] = $uid;
}
}
}
if (drush_drupal_major_version() >= 7) {
$rid_query = db_query("SELECT rid FROM {role} WHERE name = :role", array(':role' => $role));
}
else {
$rid_query = db_query("SELECT rid FROM {role} WHERE name = '%s'", $role);
}
if (!empty($uids)) {
if ($rid = drush_db_result($rid_query)) {
drush_op('user_multiple_role_edit', $uids, 'remove_role', $rid);
foreach($uids as $uid) {
drush_log(dt("Removed the %role role from uid %uid", array('%role' => $role, '%uid' => $uid)), 'success');
}
}
else {
return drush_set_error("There is no role named: \"$role\"!");
}
}
else {
return drush_set_error("Could not find any valid uids!");
}
}
/**
* Creates a new user account.
*/
function drush_user_create($name) {
$mail = drush_get_option('mail');
$pass = drush_get_option('password');
$new_user = array(
'name' => $name,
'pass' => $pass,
'mail' => $mail,
'access' => '0',
'status' => 1,
);
if (drush_drupal_major_version() >= 7) {
$result = db_query("SELECT uid FROM {users} WHERE name = :name OR mail = :mail", array(':name' => $name, ':mail' => $new_user['mail']));
}
else {
$result = db_query("SELECT uid FROM {users} WHERE name = '%s' OR mail = '%s'", $name, $new_user['mail']);
}
if (drush_db_result($result) === FALSE) {
if (!drush_get_context('DRUSH_SIMULATE')) {
$new_user_object = user_save(NULL, $new_user, NULL);
if ($new_user_object !== FALSE) {
_drush_user_print_info($new_user_object->uid);
return $new_user_object->uid;
}
else {
drush_set_error("Could not create a new user account with the name " . $name . "!");
}
}
}
else {
drush_set_error("There is already a user account with the name " . $name . " or email address " . $new_user['mail'] . "!");
}
}
/**
* Cancels a user account.
*/
function drush_user_cancel($name) {
if (drush_drupal_major_version() >= 7) {
$result = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $name));
}
else {
$result = db_query("SELECT uid FROM {users} WHERE name = '%s'", $name);
}
$uid = drush_db_result($result);
if ($uid !== FALSE) {
drush_print("Cancelling the user account with the following information:");
_drush_user_print_info($uid);
if (drush_get_option('delete-content') && drush_drupal_major_version() >= 7) {
drush_print("All content created by this user will be deleted!");
}
if (drush_confirm('Cancel user account?: ')) {
if (drush_drupal_major_version() >= 7) {
if (drush_get_option('delete-content')) {
user_cancel(array(), $uid, 'user_cancel_delete');
}
else {
user_cancel(array(), $uid, 'user_cancel_reassign');
}
// I got the following technique here: http://drupal.org/node/638712
$batch =& batch_get();
$batch['progressive'] = FALSE;
batch_process();
}
else {
user_delete(array(), $uid);
}
}
}
else {
drush_set_error("Could not find a user account with the name " . $name . "!");
}
}
/**
* Sets the password for the account with the given username
*/
function drush_user_password($name) {
$pass = drush_get_option('password');
if (empty($pass)) {
return drush_set_error("You must specify a password!");
}
if (drush_drupal_major_version() >= 7) {
$user = user_load_by_name($name);
}
else {
$user = user_load(array('name' => $name));
}
if ($user !== FALSE) {
if (!drush_get_context('DRUSH_SIMULATE')) {
$user_object = user_save($user, array('pass' => $pass));
if ($user_object === FALSE) {
drush_set_error("Could not change the password for the user account with the name " . $name . "!");
}
}
}
else {
drush_set_error("The user account with the name " . $name . " could not be loaded!");
}
}
/**
* Displays a one time login link for the given user.
*/
function drush_user_login($name = NULL) {
if (empty($name)) {
$user = user_load(1);
$name = '[uid 1]';
}
elseif (drush_drupal_major_version() >= 7) {
$user = user_load_by_name($name);
}
else {
$user = user_load(array('name' => $name));
}
if ($user !== FALSE) {
$link = user_pass_reset_url($user);
drush_print($link);
return $link;
}
else {
drush_set_error("The user account with the name " . $name . " could not be loaded!");
}
}
/**
* Print information about a given uid
*/
function _drush_user_print_info($uid) {
if (drush_drupal_major_version() >= 7) {
$userinfo = user_load($uid);
}
else {
$userinfo = user_load(array('uid' => $uid));
}
if (drush_get_option('full')) {
$userinfo = (array)$userinfo;
$userinfo_pipe = array();
unset($userinfo['data']);
unset($userinfo['block']);
unset($userinfo['form_build_id']);
foreach($userinfo as $key => $val) {
if (is_array($val)) {
drush_print($key . ': ');
drush_print_r($val);
$userinfo_pipe[] = '"' . implode(",", $val) . '"';
}
else {
if ($key === 'created' OR $key === 'access' OR $key === 'login') {
drush_print($key . ': ' . format_date($val));
$userinfo_pipe[] = $val;
}
else {
drush_print($key . ': ' . $val);
$userinfo_pipe[] = $val;
}
}
}
drush_print_pipe(implode(",", $userinfo_pipe));
drush_print_pipe("\n");
}
else {
$userinfo_short = array(
'User ID' => $userinfo->uid,
'User name' => $userinfo->name,
'User mail' => $userinfo->mail,
);
$userinfo_short['User roles'] = implode(', ', $userinfo->roles);
$userinfo->status ? $userinfo_short['User status'] = 'active' : $userinfo_short['User status'] = 'blocked';
drush_print_table(drush_key_value_to_array_table($userinfo_short));
drush_print_pipe("$userinfo->name, $userinfo->uid, $userinfo->mail, $userinfo->status, \"" . implode(', ', $userinfo->roles) . "\"\n");
}
}
/**
* Get uid(s) from a uid, user name, or email address.
* Returns a uid, or FALSE if none found.
*/
function _drush_user_get_uid($search) {
// We use a DB query while looking for the uid to keep things speedy.
$uids = array();
if (is_numeric($search)) {
if (drush_drupal_major_version() >= 7) {
$uid_query = db_query("SELECT uid, name FROM {users} WHERE uid = :uid OR name = :name", array(':uid' => $search, ':name' => $search));
}
else {
$uid_query = db_query("SELECT uid, name FROM {users} WHERE uid = %d OR name = '%d'", $search, $search);
}
}
else {
if (drush_drupal_major_version() >= 7) {
$uid_query = db_query("SELECT uid, name FROM {users} WHERE mail = :mail OR name = :name", array(':mail' => $search, ':name' => $search));
}
else {
$uid_query = db_query("SELECT uid, name FROM {users} WHERE mail = '%s' OR name = '%s'", $search, $search);
}
}
while ($uid = drush_db_fetch_object($uid_query)) {
$uids[$uid->uid] = $uid->name;
}
switch (count($uids)) {
case 0:
return drush_set_error("Could not find a uid for the search term '" . $search . "'!");
break;
case 1:
return array_pop(array_keys($uids));
break;
default:
drush_print('More than one user account was found for the search string "' . $search . '".');
return(drush_choice($uids, 'Please choose a name:', '!value (uid=!key)'));
}
}

View File

@@ -0,0 +1,135 @@
<h1>The Drush Bootstrap Process</h1>
<p>
When preparing to run a command, drush works by "bootstrapping"
the Drupal environment in very much the same way that is done
during a normal page request from the web server, so most drush
commands run in the context of a fully-initialized website.
<p>
For efficiency and convenience, some drush commands can work
without first bootstrapping a Drupal site, or by only partially
bootstrapping a site. This is more efficient, because there is
sometimes a slight delay involved with bootstrapping, especially
in some of the later stages. It is also a matter of convenience,
because some commands are useful to use even when you do not
have a working Drupal site available to bootstrap. For example,
you can use drush to download Drupal with `drush dl drupal`. This
obviously does not require any bootstrapping to work.
<p>
The drush bootstrapping process is also very closely related with
drush configuration files. At each bootstrap phase, drush may load
additional configuration files that may contain additional drush
settings. This has two very important connotations. First, settings
that are only loaded in a later bootstrap phase are not available
for commands that do not reach that bootstrap phase. Second, it
is possible to alter drush behavior on a per-site basis by applying
settings in a site-specific configuration file. See
`drush topic docs-configuration` for details on drush configuration
files.
<h2>DRUSH_BOOTSTRAP_DRUSH</h2>
<p>
Configuration files loaded during this phase:<ul>
<li>Drush installation folder.
<li>System wide configuration folder (e.g. /etc/drush/drushrc.php).
<li>User's .drush folder (i.e. ~/.drush/drushrc.php).
<li>In any location, as specified by the --config (-c) option.
</ul><p>
Only bootstrap Drush, without any Drupal specific code.
<p>
Any code that operates on the Drush installation, and not specifically
any Drupal directory, should bootstrap to this phase.
<h2>DRUSH_BOOTSTRAP_DRUPAL_ROOT</h2>
<p>
Configuration files loaded during this phase:<ul>
<li>Drupal installation root.
</ul><p>
Set up and test for a valid drupal root, either through the -r/--root options,
or evaluated based on the current working directory.
<p>
Any code that interacts with an entire Drupal installation, and not a specific
site on the Drupal installation should use this bootstrap phase.
<h2>DRUSH_BOOTSTRAP_DRUPAL_SITE</h2>
<p>
Configuration files loaded during this phase:<ul>
<li>Drupal site folder (e.g sites/{default|example.com}/drushrc.php).
</ul><p>
Set up a Drupal site directory and the correct environment variables to
allow Drupal to find the configuration file.
<p>
If no site is specified with the -l / --uri options, Drush will assume the
site is 'default', which mimics Drupal's behaviour.
<p>
If you want to avoid this behaviour, it is recommended that you use the
DRUSH_BOOTSTRAP_DRUPAL_ROOT bootstrap phase instead.
<p>
Any code that needs to modify or interact with a specific Drupal site's
settings.php file should bootstrap to this phase.
<h2>DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION</h2>
<p>
Load the settings from the Drupal sites directory.
<p>
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.
<p>
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.
<h2>DRUSH_BOOTSTRAP_DRUPAL_DATABASE</h2>
<p>
Connect to the Drupal database using the database credentials loaded
during the previous bootstrap phase.
<p>
This phase is analogous to the DRUPAL_BOOTSTRAP_DATABASE bootstrap phase in
Drupal.
<p>
Any code that needs to interact with the Drupal database API needs to
be bootstrapped to at least this phase.
<h2>DRUSH_BOOTSTRAP_DRUPAL_FULL</h2>
<p>
Fully initialize Drupal.
<p>
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.
<p>
Any code that interacts with the general Drupal API should be
bootstrapped to this phase.
<h2>DRUSH_BOOTSTRAP_DRUPAL_LOGIN</h2>
<p>
Log in to the initialiased Drupal site.
<p>
This bootstrap phase is used after the site has been
fully bootstrapped.
<p>
This phase will log you in to the drupal site with the username
or user ID specified by the --user/ -u option.
<p>
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.
<h2>DRUSH_BOOTSTRAP_MAX</h2>
<p>
This is not an actual bootstrap phase. Commands that use
DRUSH_BOOTSTRAP_MAX will cause drush to bootstrap as far
as possible, and then run the command regardless of the
bootstrap phase that was reached. This is useful for drush
commands that work without a bootstrapped site, but that
provide additional information or capabilities in the presence
of a bootstrapped site. For example, `drush pm-releases modulename`
works without a bootstrapped Drupal site, but will include
the version number for the installed module if a Drupal site
has been bootstrapped.

View File

@@ -0,0 +1,254 @@
<h1>Creating Custom Drush Commands</h1>
<p>
Creating a new drush command is very easy. There are
four simple steps.
<ol>
<li>Create a command file called COMMANDFILE.drush.inc
<li>Implement the function COMMANDFILE_drush_help(). Optional.
<li>Implement the function COMMANDFILE_drush_command()
<li>Implement the functions that your commands will call.
These will usually be named drush_COMMANDFILE_COMMANDNAME().
</ol><p>
For an example drush command, see examples/sandwich.drush.inc.
The steps for implementing your command are explained in more
detail below.
<h2>Create COMMANDFILE.drush.inc</h2>
<p>
The name of your drush command is very important. It must end
in ".drush.inc" to be recognized as a drush command. The part
of the filename that comes before the ".drush.inc" becomes the
name of the commandfile. Your commandfile name is used by
drush to compose the names of the functions it will call, so
choose wisely.
<p>
The example drush command, 'make-me-a-sandwich', is stored
in the 'sandwich' commandfile, 'sandwich.drush.inc'.
<p>
Drush searches for commandfiles in the following locations:
<ul>
<li>The "/path/to/drush/commands" folder.
<li>Folders listed in the 'include' option (see `drush topic docs-configuration`).
<li>The system-wide drush commands folder, e.g. /usr/share/drush/commands
<li>The ".drush" folder in the user's HOME folder.
<li>All modules in the current Drupal installation
</ul> <p>
Note that modules in the current Drupal installation will only
be considered if drush has bootstrapped to at least the DRUSH_BOOSTRAP_SITE
level. Usually, when working with a Drupal site, drush will
bootstrap to DRUSH_BOOTSTRAP_FULL; in this case, only the drush
commandfiles in enabled modules will be considered eligible for
loading. If a drush only bootstraps to DRUSH_BOOTSTRAP_SITE,
though, then all drush commandfiles will be considered, whether the
module is enabled or not. See `drush topic docs-bootstrap` for
more information on bootstrapping.
<p>
Additionally, drush commandfiles may optionally define a function
COMMANDFILE_drush_load() in the file COMMANDFILE.drush.load.inc.
If this function returns FALSE, then the commandfile will not be loaded.
<h2>Implement COMMANDFILE_drush_help()</h2>
<p>
The drush_help hook is an optional place to describe a command in long form. If
the command only requires a brief description, use the description key in
COMMANDFILE_drush_command(). The drush_help hook for the 'sandwich' commandfile looks
like this:
<pre>
function sandwich_drush_help($section) {
switch ($section) {
case 'drush:make-me-a-sandwich':
return dt("... brief help summary goes here ...");
}
}
</pre><p>
Note that the command name is prepended with 'drush:' in
the drush_help hook. Your commandfile may implement
multiple commands; to do so, just add more 'case' statements
to the switch, one for each command.
<h2>Implement COMMANDFILE_drush_command()</h2>
<p>
The drush_command hook is the most important part of the
commandfile. It returns an array of items that define
how your commands should be called, and how they work.
Drush commands are very similar to the Drupal menu system.
The elements that can appear in a drush command definition
are shown below.
<ul>
<li>'aliases':
Provides a list of shorter names for the command.
For example, pm-download may also be called via `drush dl`.
If the alias is used, drush will substitute back in the
primary command name, so pm-download will still be used
to generate the command hook, etc.
<li>'deprecated-aliases':
Works just like 'aliases', but does not
appear in help. Used in instances where the drush
maintainers intend to eventually remove support for a
command alias. If someone runs a drush command using a
deprecated alias, drush will print a warning message.
<li>'command hook':
Change the name of the function drush will
call to execute the command from drush_COMMANDFILE_COMMANDNAME()
to drush_COMMANDFILE_COMMANDHOOK(), where COMMANDNAME is the
original name of the command, and COMMANDHOOK is the value
of the 'command hook' item.
<li>'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.
<li>'callback arguments':
An array of arguments to pass to the callback.
The command line arguments, if any, will appear after the
callback arguments in the function parameters.
<li>'description':
Description of the command.
<li>'arguments':
An array of arguments that are understood by the command.
Used by `drush help` only.
<li>'options':
An array of options that are understood by the command.
Used by `drush help` only.
<li>'examples':
An array of examples that are understood by the command.
Used by `drush help` only.
<li>'scope':
One of 'system', 'project', 'site'. Not currently used.
<li>'bootstrap':
Drupal bootstrap level. Valid values are:
<ul>
<li>DRUSH_BOOTSTRAP_DRUSH
<li>DRUSH_BOOTSTRAP_DRUPAL_ROOT
<li>DRUSH_BOOTSTRAP_DRUPAL_SITE
<li>DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION
<li>DRUSH_BOOTSTRAP_DRUPAL_DATABASE
<li>DRUSH_BOOTSTRAP_DRUPAL_FULL
<li>DRUSH_BOOTSTRAP_DRUPAL_LOGIN
<li>DRUSH_BOOTSTRAP_MAX
</ul>
See `drush topic docs-bootstrap`.
<li>'core':
Drupal major version required. Append a '+' to indicate 'and later versions.'
<li>'drupal dependencies':
Drupal modules required for this command.
<li>'drush dependencies':
Other drush commandfiles required for this command.
<li>'topics':
Provides a list of topic commands that are related in
some way to this command. Used by `drush help` only.
<li>'topic':
Set to TRUE if this command is a topic, callable from the
`drush docs-topics` command.
</ul><p>
The 'sandwich' drush_command hook looks like this:
<pre>
function sandwich_drush_command() {
$items = array();
$items['make-me-a-sandwich'] = array(
'description' => "Makes a delicious sandwich.",
'arguments' => array(
'filling' => 'The type of the sandwich (turkey, cheese, etc.)',
),
'options' => array(
'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)',
),
'examples' => array(
'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
),
'aliases' => array('mmas'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
);
return $items;
}
</pre><p>
Most of the items in the 'make-me-a-sandwich' command
definition have no effect on execution, and are used only
by `drush help`. The exceptions are 'aliases' (described
above) and 'bootstrap'. As previously mentioned,
`drush topic docs-bootstrap` explains the drush bootstrapping
process in detail.
<h2>Implement drush_COMMANDFILE_COMMANDNAME()</h2>
<p>
The 'make-me-a-sandwich' command in sandwich.drush.inc
is defined as follows:
<pre>
function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
... implementation here ...
}
</pre><p>
If a user runs `drush make-me-a-sandwich` with no command line
arguments, then drush will call drush_sandwich_make_me_a_sandwich()
with no function parameters; in this case, $filling will take on
the provided default value, 'ascii'. (If there is no default
value provided, then the variable will be NULL, and a warning
will be printed.) Running `drush make-me-a-sandwich ham` will
cause drush to call drush_sandwich_make_me_a_sandwich('ham'). In
the same way, commands that take two command line arguments can
simply define two functional parameters, and a command that takes
a variable number of command line arguments can use the standard
php function func_get_args() to get them all in an array for easy
processing.
<p>
Note that drush will actually call a sequence of functions before
and after your drush command function. One of these hooks is the
"validate" hook. The 'sandwich' commandfile provides a validate
hook for the 'make-me-a-sandwich' command:
<pre>
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.'));
}
}
</pre><p>
The validate function should call drush_set_error and return
its result if the command cannot be validated for some reason.
See `drush topic docs-policy` for more information on defining
policy functions with validate hooks, and `drush topic docs-api`
for information on how the command hook process works. Also,
the list of defined drush error codes can be found in
`drush topic docs-errorcodes`.

View File

@@ -0,0 +1,73 @@
<h1>Drush Contexts</h1>
<p>
The drush contexts API acts as a storage mechanism for all options,
arguments and configuration settings that are loaded into drush.
<p>
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.
<p>
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.
<p>
This API defines several contexts that are used by default.
<h2>Argument contexts</h2>
<p>
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().
<ul>
<li>command : The drush command being executed.</li>
<li>arguments : Any additional arguments that were specified.</li>
</ul>
<h2>Setting contexts</h2>
<p>
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.
<p>
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.
<p>
Drush commands may also choose to save settings for a specific context to
the matching configuration file through the drush_save_config() function.
<h2>Available Setting contexts</h2>
<p>
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.
<p>
Specified by the script itself :
<ul>
<li>process : Generated in the current process.
<li>cli : Passed as --option=value to the command line.
<li>stdin : Passed as a JSON encoded string through stdin.
<li>alias : Defined in an alias record, and set in the
alias context whenever that alias is used.
<li>specific : Defined in a command-specific option record, and
set in the command context whenever that command is used.
</ul>
<p>
Specified by config files :
<ul>
<li>custom : Loaded from the config file specified by --config or -c
<li>site : Loaded from the drushrc.php file in the Drupal site directory.
<li>drupal : Loaded from the drushrc.php file in the Drupal root directory.
<li>user : Loaded from the drushrc.php file in the user's home directory.
<li>drush : Loaded from the drushrc.php file in the $HOME/.drush directory.
<li>system : Loaded from the drushrc.php file in the system's $PREFIX/etc/drush directory.
<li>drush : Loaded from the drushrc.php file in the same directory as drush.php.
</ul>
<p>
Specified by the script, but has the lowest priority :
<ul>
<li>default : The script might provide some sensible defaults during init.
</ul>

View File

@@ -0,0 +1,270 @@
<?php
/**
* @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 commandfile named "hook", the following hooks are called, in
* order, for the command "COMMAND":
*
* 0. drush_COMMAND_init()
* 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()
*
* Note that the drush_COMMAND_init() hook is only for use by the
* commandfile that defines the command.
*
* If any of hook function fails, either by calling drush_set_error
* or by returning FALSE as its function result, then the rollback
* mechanism is called. To fail with an error, call drush_set_error:
*
* return drush_set_error('MY_ERROR_CODE', dt('Error message.'));
*
* To allow the user to confirm or cancel a command, use drush_confirm
* and drush_user_abort:
*
* if (!drush_confirm(dt('Are you sure?'))) {
* return drush_user_abort();
* }
*
* The rollback mechanism 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_COMMAND_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() {
}
/**
* Initialize a command prior to validation. If a command
* needs to bootstrap to a higher level, this is best done in
* the command init hook. It is permisible to bootstrap in
* any hook, but note that if bootstrapping adds more commandfiles
* (*.drush.inc) to the commandfile list, the newly-added
* commandfiles will not have any hooks called until the next
* phase. For example, a command that calls drush_bootstrap_max()
* in drush_hook_COMMAND() would only permit commandfiles from
* modules enabled in the site to participate in drush_hook_post_COMMAND()
* hooks.
*/
function drush_COMMAND_init() {
drush_bootstrap_max();
}
/**
* 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() {
}
/*
* A commandfile may choose to decline to load for the current bootstrap
* level by returning FALSE. This hook must be placed in MODULE.drush.load.inc.
* @see drush_commandfile_list().
*/
function hook_drush_load() {
}
/**
* Take action after a project has been downloaded.
*/
function hook_drush_pm_post_download($project, $release) {
}
/**
* 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 copied to after being downloaded.
*
* See @pm_drush_pm_download_destination_alter().
*/
function hook_drush_pm_download_destination_alter(&$project, $release) {
if ($some_condition) {
$project['project_install_location'] = '/path/to/install/to/' . $project['project_dir'];
}
}
/**
* Add information to the upgrade project map; this information
* will be shown to the user when upgrading Drupal to the next
* major version if the module containing this hook is enabled.
*
* @see drush_upgrade_project_map().
*/
function hook_drush_upgrade_project_map_alter(&$project_map) {
$project_map['warning']['hook'] = dt("You need to take special action before upgrading this module. See http://mysite.com/mypage for more information.");
}
/**
* Sql-sync sanitization example. This is equivalent to
* the built-in --sanitize option of sql-sync, but simplified
* to only work with default values on Drupal 6 + mysql.
*
* @see sql_drush_sql_sync_sanitize().
*/
function hook_drush_sql_sync_sanitize($source) {
drush_sql_register_post_sync_op('my-sanitize-id',
dt('Reset passwords and email addresses in user table'),
"update users set pass = MD5('password'), mail = concat('user+', uid, '@localhost') where uid > 0;");
}
/**
* Take action before modules are disabled in a major upgrade.
* Note that when this hook fires, it will be operating on a
* copy of the database.
*/
function drush_hook_pre_site_upgrade_prepare() {
// site upgrade prepare will disable contrib_extensions and
// uninstall the uninstall_extension
$contrib_extensions = func_get_args();
$uninstall_extensions = explode(',', drush_get_option('uninstall', ''));
}
/**
* Add help components to a command
*/
function hook_drush_help_alter(&$command) {
if ($command['command'] == 'sql-sync') {
$command['options']['myoption'] = "Description of modification of sql-sync done by hook";
$command['sub-options']['sanitize']['my-sanitize-option'] = "Description of sanitization option added by hook (grouped with --sanitize option)";
}
}
/**
* Add/edit options to cache-clear command
*/
function hook_drush_cache_clear(&$types) {
$types['views'] = 'views_invalidate_cache';
}
/*
* Make shell aliases and other .bashrc code available during core-cli command.
*
* @return
* Bash code typically found in a .bashrc file.
*
* @see core_cli_bashrc() for an example implementation.
*/
function hook_cli_bashrc() {
$string = "
alias siwef='drush site-install wef --account-name=super --account-mail=me@wef'
alias dump='drush sql-dump --structure-tables-key=wef --ordered-dump'
";
return $string;
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,80 @@
<h1>The Drush Shell Scripts</h1>
<p>
A drush shell script is any Unix shell script file that has
its "execute" bit set (i.e., via `chmod +x myscript.drush`)
and that begins with a specific line:
<pre>
#!/usr/bin/env drush
</pre>
- or -
<pre>
#!/full/path/to/drush
</pre><p>
The former is the usual form, and is more convenient in that
it will allow you to run the script regardless of where drush
has been installed on your system, as long as it appears in
your PATH. The later form allows you to specify the drush
command add options to use, as in:
<pre>
#!/full/path/to/drush php-script --some-option
</pre><p>
Adding specific options is important only in certain cases,
described later; it is usually not necessary.
<p>
Drush scripts do not need to be named "*.drush" or "*.script";
they can be named anything at all. To run them, make sure they
are executable (`chmod +x helloworld.script`) and then run them
from the shell like any other script.
<p>
There are two big advantages to drush scripts over bash scripts:
<ul>
<li>They are written in php
<li>drush can bootstrap your Drupal site before
running your script.
</ul><p>
To bootstrap a Drupal site, provide an alias to the site to
bootstrap as the first commandline argument.
<p>
For example:
<pre>
$ helloworld.script @dev a b c
</pre><p>
If the first argument is a valid site alias, drush will remove
it from the arument list and bootstrap that site, then run
your script. The script itself will not see @dev on its
argument list. If you do not want drush to remove the first
site alias from your scripts argument list (e.g. if your script
wishes to syncronise two sites, specified by the first two
arguments, and does not want to bootstrap either of those
two sites), then fully specify the drush command (php-script)
and options to use, as shown above. By default, if the drush
command is not specified, drush will provide the following default
line:
<pre>
#!/full/path/to/drush php-script --bootstrap-to-first-arg
</pre><p>
It is the option --bootstrap-to-first-arg that causes drush to
pull off the first argument and bootstrap it. The way to get rid
of that option is to specify the php-script line to run, and leave
it off, like so:
<pre>
#!/full/path/to/drush php-script
</pre><p>
Note that 'php-script' is the only built-in drush command that
makes sense to put on the "shebang" ("#!" is pronounced "shebang")
line. However, if you wanted to, you could implement your own
custom version of php-script (e.g. to preprocess the script input,
perhaps), and specify that command on the shebang line.
<p>
Drush scripts can access their arguments via the drush_shift()
function:
<pre>
while ($arg = drush_shift()) {
drush_print($arg);
}
</pre><p>
Options are available via drush_get_option('option-name').
<p>
See the example drush script in `drush topic docs-examplescript`,
and the list of drush error codes in `drush topic docs-errorcodes`.

View File

@@ -0,0 +1,100 @@
<h1>Using Drush to Upgrade Drupal 6.x to Drupal 7.x</h1>
<p>
The drush site-upgrade command supports upgrades from Drupal 6.x
to Drupal 7.x. Upgrades from Drupal 7.x to Drupal 8.x will be
supported in the future. Use the drush pm-update command to do minor verison
updates for Drupal 5.x, 6.x and 7.x.
<p>
To begin, consult the UPGRADE.txt file from the root folder of the
version of Drupal you are upgrading to. Drush will handle some of
the steps described there, but not all. In particular, you should
make sure that your current Drupal installation is running on the
most recent minor version available.
<p>
n.b. At the time of this writing, Drupal 6.20 and Drupal 7.0 were
the most recent versions available. Always check primary sources
prior to upgrading to see if anything may have changed.
<h2>Upgrade a Copy of your Site</h2>
<p>
Drush will always upgrade <i>from</i> the specified Drupal site
<i>to</i> an empty Drupal site. It is necessary to create a
site alias to describe the destination site. Site aliases are
described in `drush topic docs-aliases`. A canonical site alias
is adequate to define your target site for upgrade:
<p>
$aliases['onward'] = { <br>
'root' => '/path/to/upgraded/drupalroot',<br>
'uri' => 'http://onward.mysite.org',<br>
}<br>
<p>
Optionally, you might also want to define 'db_url' to specify
the name of your target database. If you do not, drush will
compose a database name for you based on the name of your site alias.
<p>
<h2>Running the `site-upgrade` Command</h2>
<p>
Drush will upgrade the current bootstrapped site, and will
put the result in the target site specified by the argument
to the command. For example:
<p>
drush @from site-upgrade @onward
<p>
The site-upgrade command will perform the following operations:
<ol>
<li>Download the next major release of Drupal and store the files
in the target site.</li>
<li>Write a new settings.php file with an appropriate databases
structure for the new target site.</li>
<li>Make a copy of the SQL database for the new site.</li>
<li>The default theme for the new site will be set to "garland",
and the admin theme will be set to "seven".</li>
<li>All non-core extensions will be disabled in the target database,
and updatedb will be run on Drupal core.</li>
<li>All of the modules disabled in the previous step will be
downloaded again, if they have an appropriate version for the
target Drupal site available.</li>
<li>updatedb will be run again to update the non-core modules.</li>
<li>All of the non-core modules will be enabled again.</li>
</ol>
<p>
Before drush begins this process, it will first print out a list
of warnings for any modules that may not be ready to upgrade yet.
Please read all of these messages carefully before proceding.
Here are some of the situations you may encounter.
<ul>
<li>If a module has no <i>recommended</i> release, then drush
will continue, downloading an available release for the upgrade.</li>
<li>If a module has no release at all, then drush will skip it.
The module's data will remain in the database.</li>
<li>You may discover that some modules, such as date_api, might
cause the Drupal core updatedb to fail. In this instance, you
may use the --uninstall option to list the modules to uninstall
prior to upgrade. Modules uninstalled in this manner will have
all of their data removed from the target database; the database
of the source site is not affected.</li>
</ul>
<p>
When drush is enabling the modules in the upgraded site, if there
are any new dependencies, they will also be identified and downloaded
if possible. For example, views-7.x depends on the ctools module
from the Chaos tool suite, whereas views-6 did not; drush will therefore
download and enable ctools when upgrading the views module from
Drupal 6 to Drupal 7.
<p>
There will still be some work left to do after the site-upgrade
command has completed; for example, you will still need to port your
theme and any custom modules to the new version of Drupal, and some
contrib modules may require additional work to update their configuration
settings. Using site-upgrade will all the same save you a considerable
amount of time. There is no risk in trying it; if it does not work,
you can always start over, or run it at a later date when more contrib
modules have been updated. If you do run site-upgrade a second time
on the same site, drush will ask you if you want to replace the existing
target site, effectively starting over from the beginning, or re-use
the site that is already present. In the later case, drush will use
the existing code, but will re-copy the source database and run updatedb
on it again. This will all you to attempt to fix any non-working contrib
modules yourself to get your site-upgrade working.
<p>
Enjoy!

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env sh
# $Id$
#
# 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, try to determine and export the number of columns.
# We do not want to run $(tput cols) if $TERM is empty or "dumb", because
# if we do, tput will output an undesirable error message to stderr. If
# we redirect stderr in any way, e.g. $(tput cols 2>/dev/null), then the
# error message is suppressed, but tput cols becomes confused about the
# terminal and prints out the default value (80).
if [ -z $COLUMNS ] && [ -n "$TERM" ] && [ "$TERM" != dumb ] ; then
# Note to cygwin users: install the ncurses package to get tput command.
if COLUMNS=$(tput cols); then
export COLUMNS
fi
fi
if [ ! -z "$DRUSH_PHP" ] ; then
# Use the DRUSH_PHP environment variable if it is available.
php="$DRUSH_PHP"
else
# Default to using the php that we find on the PATH.
# Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926
php=`which php`
# 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
php=`which php-cli`
fi
# Special case for *AMP installers, since they normally don't set themselves as the default cli php out of the box.
for amp_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 $amp_php ]; then
php=$amp_php
fi
done
fi
# Check to see if the user has provided a php.ini file or drush.ini file in any conf dir
# Last found wins, so search in reverse priority order
for conf_dir in $(dirname "$SELF_PATH") /etc/drush $HOME/.drush ; do
if [ -f $conf_dir/php.ini ] ; then
drush_php_ini=$conf_dir/php.ini
fi
if [ -f $conf_dir/drush.ini ] ; then
drush_php_override=$conf_dir/drush.ini
fi
done
# Add in the php file location and/or the php override variables as appropriate
if [ "x$drush_php_ini" != "x" ] ; then
php="$php --php-ini $drush_php_ini"
fi
if [ "x$drush_php_override" != "x" ] ; then
drush_override_vars=`grep '^[a-z_A-Z0-9]\+ *=' $drush_php_override | sed -e 's|\([^ =]*\) *= *\(.*\)|\1="\2"|' -e 's| ||g' -e 's|^|-d |' | tr '\n\r' ' '`
php="$php $drush_override_vars"
fi
# Pass in the path to php so that drush knows which one
# to use if it re-launches itself to run subcommands
exec $php "$SCRIPT_PATH" "$@" --php="$php"

View File

@@ -0,0 +1,3 @@
@echo off
REM See http://drupal.org/node/506448 for more information.
@php.exe "%~dp0drush.php" %*

View File

@@ -0,0 +1,8 @@
drush_version=4.5
; Information added by drupal.org packaging script on 2011-08-12
version = "7.x-4.5"
core = "7.x"
project = "drush"
datestamp = "1313123851"

View File

@@ -0,0 +1,256 @@
#!/usr/bin/env php
<?php
/**
* @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 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 an error handler and a shutdown function
set_error_handler('drush_error_handler');
register_shutdown_function('drush_shutdown');
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(FALSE, TRUE);
drush_set_context('DRUSH_BOOTSTRAP_PHASE', DRUSH_BOOTSTRAP_NONE);
// We need some global options processed at this early stage. Namely --debug.
drush_parse_args();
_drush_bootstrap_global_options();
$return = '';
$command_found = FALSE;
foreach ($phases as $phase) {
if (drush_bootstrap_to_phase($phase)) {
$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)) {
$bootstrap_result = drush_bootstrap_to_phase($command['bootstrap']);
drush_enforce_requirement_bootstrap_phase($command);
drush_enforce_requirement_core($command);
drush_enforce_requirement_drupal_dependencies($command);
drush_enforce_requirement_drush_dependencies($command);
if ($bootstrap_result && 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);
// prevent a '1' at the end of the output
if ($return === TRUE) {
$return = '';
}
if (drush_get_context('DRUSH_DEBUG') && !drush_get_context('DRUSH_QUIET')) {
drush_print_timers();
}
drush_log(dt('Peak memory usage was !peak', array('!peak' => drush_format_size(memory_get_peak_usage()))), 'memory');
break;
}
}
}
else {
break;
}
}
if (!$command_found) {
// If we reach this point, 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)));
}
// Set errors that ocurred in the bootstrap phases.
$errors = drush_get_context('DRUSH_BOOTSTRAP_ERRORS', array());
foreach ($errors as $code => $message) {
drush_set_error($code, $message);
}
}
// 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) && !drush_get_context('DRUSH_USER_ABORT', FALSE)) {
$php_error_message = '';
if ($error = error_get_last()) {
$php_error_message = "\n" . dt('Error: !message in !file, line !line', array('!message' => $error['message'], '!file' => $error['file'], '!line' => $error['line']));
}
// We did not reach the end of the drush_main function,
// this generally means somewhere in the code a call to exit(),
// was made. We catch this, so that we can trigger an error in
// those cases.
drush_set_error("DRUSH_NOT_COMPLETED", dt("Drush command terminated abnormally due to an unrecoverable error.!message", array('!message' => $php_error_message)));
// Attempt to give the user some advice about how to fix the problem
_drush_postmortem();
}
$phase = drush_get_context('DRUSH_BOOTSTRAP_PHASE');
if (drush_get_context('DRUSH_BOOTSTRAPPING')) {
switch ($phase) {
case DRUSH_BOOTSTRAP_DRUPAL_FULL :
ob_end_clean();
_drush_log_drupal_messages();
drush_set_error('DRUSH_DRUPAL_BOOTSTRAP_ERROR');
break;
}
}
if (drush_get_context('DRUSH_BACKEND')) {
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();
}
/**
* For now, drush skips end of page processing on D7. Doing so could write
* cache entries to module_implements and lookup_cache that don't match web requests.
*/
// if (drush_drupal_major_version() >= 7 && function_exists('drupal_page_footer')) {
// drupal_page_footer();
// }
// this way drush_return_status will always be the last shutdown function (unless other shutdown functions register shutdown functions...)
// and won't prevent other registered shutdown functions (IE from numerous cron methods) from running by calling exit() before they get a chance.
register_shutdown_function('drush_return_status');
}
function drush_return_status() {
exit((drush_get_error()) ? DRUSH_FRAMEWORK_ERROR : DRUSH_SUCCESS);
}
/**
* Log the given user in to a bootstrapped Drupal site.
*
* @param mixed
* Numeric user id or user name.
*
* @return boolean
* TRUE if user was logged in, otherwise FALSE.
*/
function drush_drupal_login($drush_user) {
global $user;
if (drush_drupal_major_version() >= 7) {
$user = is_numeric($drush_user) ? user_load($drush_user) : user_load_by_name($drush_user);
}
else {
$user = user_load(is_numeric($drush_user) ? array('uid' => $drush_user) : array('name' => $drush_user));
}
if (empty($user)) {
if (is_numeric($drush_user)) {
$message = dt('Could not login with user ID #!user.', array('!user' => $drush_user));
if ($drush_user === 0) {
$message .= ' ' . dt('This is typically caused by importing a MySQL database dump from a faulty tool which re-numbered the anonymous user ID in the users table. See !link for help recovering from this situation.', array('!link' => 'http://drupal.org/node/1029506'));
}
}
else {
$message = dt('Could not login with user account `!user\'.', array('!user' => $drush_user));
}
return drush_set_error('DRUPAL_USER_LOGIN_FAILED', $message);
}
else {
$name = $user->name ? $user->name : variable_get('anonymous', t('Anonymous'));
drush_log(dt('Successfully logged into Drupal as !name', array('!name' => $name . " (uid=$user->uid)")), 'bootstrap');
}
return TRUE;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,210 @@
<?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 options.
* 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.
*
* Alias groups (aliases stored together in files called
* GROUPNAME.aliases.drushrc.php, as mentioned above) also
* create an implicit namespace that is named after the group
* name.
*
* For example:
*
* # File: mysite.aliases.drushrc.php
* $aliases['dev'] = array(
* 'root' => '/path/to/drupal',
* 'uri' => 'dev.mydrupalsite.com',
* );
* $aliases['live'] = array(
* 'root' => '/other/path/to/drupal',
* 'uri' => 'mydrupalsite.com',
* );
*
* Then the following special aliases are defined:
*
* @mysite An alias named after the groupname
* may be used to reference all of the
* aliases in the group (e.g. drush @mydrupalsite status)
*
* @mysite.dev A copy of @dev
*
* @mysite.live A copy of @live
*
* Thus, aliases defined in an alias group file may be referred to
* either by their simple (short) name, or by thier full namespace-qualified
* name.
*
* To see an example alias definition for the current bootstrapped
* site, use the "site-alias" command with the built-in alias "@self":
*
* $ drush site-alias @self
*
* 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-script': The path to the 'drush' script, or to 'drush.php' or
* 'drush.bat', as desired. This is used by backend invoke when drush
* runs a drush command. The default is 'drush' on remote machines, or
* the full path to drush.php on the local machine.
* '%drush': A read-only property: points to the folder that the drush script
* is stored in.
* '%dump-dir': Path to directory that "drush sql-sync" should use to store
* sql-dump files. Helpful filenames are auto-generated.
* '%dump': Path to the file that "drush sql-sync" should use to store sql-dump file.
* '%files': Path to 'files' directory. This will be looked up if not specified.
* '%root': A reference to the Drupal root defined in the 'root' item
* in the site alias record.
* - 'command-specific': These options will only be set if the alias
* is used with the specified command. In the example below, the option
* `--no-cache` will be selected whenever the @stage alias
* is used in any of the following ways:
* drush @stage sql-sync @self @live
* drush sql-sync @stage @live
* drush sql-sync @live @stage
* In case of conflicting options, command-specific options in targets
* (source and destination) take precedence over command-specific options
* in the bootstrapped site, and command-specific options in a destination
* alias will take precedence over those in a source alias.
* - 'source-command-specific' and 'target-command-specific': Behaves exactly
* like the 'command-specific' option, but is applied only if the alias
* is used as the source or target, respectively, of an rsync or sql-sync
* command. In the example below, `--skip-tables-list=comments` whenever
* the alias @live is the target of an sql-sync command, but comments will
* be included if @live is the source for the sql-sync command.
* 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-dir' => '/path/to/dumps/',
# '%files' => 'sites/mydrupalsite.com/files',
# '%custom' => '/my/custom/path',
# ),
# 'command-specific' => array (
# 'sql-sync' => array (
# 'no-cache' => TRUE,
# ),
# ),
# );
#$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',
# 'target-command-specific' => array (
# 'sql-sync' => array (
# 'skip-tables-list' => 'comments',
# ),
# ),
# );

View File

@@ -0,0 +1,20 @@
# Examples of valid statements for a drush bashrc file. Use this file to cut down on
# typing of options and avoid mistakes.
#
# Rename this file to .bashrc and optionally copy it to one of
# four convenient places:
#
# 1. User's $HOME folder (i.e. ~/.bashrc).
# 2. User's .drush folder (i.e. ~/.drush/.bashrc).
# 3. System wide configuration folder (e.g. /etc/drush/.bashrc).
# 4. System wide command folder (e.g. /usr/share/drush/command/.bashrc).
# 5. Drush installation folder
#
# Drush will search for .bashrc files whenever the drush interactive
# shell, i.e. `drush core-cli` is entered. If a configuration file
# is found in any of the above locations, it will be sourced by bash
# and merged with other configuration files encountered.
alias siwef='site-install wef --account-name=super --account-mail=me@wef'
alias dump='sql-dump --structure-tables-key=wef --ordered-dump'
alias cli-update='(drush core-cli --pipe > $HOME/.bash_aliases) && source $HOME/.bash_aliases'

View File

@@ -0,0 +1,70 @@
;
; Example of a drush php settings override file
;
; IMPORTANT: Before following the instructions in
; this file, first check to see that the cli version
; of php is installed on your system. (e.g. On
; debian systems, `sudo apt-get install php5-cli`.)
;
; Use this file in instances when your system is
; -not- configured to use separate php.ini files for
; webserver and cli use. You can determine which
; php.ini file drush is using by running "drush status".
; If the php.ini file shown is your webserver ini
; file, then rename this file, example.drush.ini,
; to drush.ini and copy it to one of the following
; locations:
;
; 1. Drush installation folder
; 2. User's .drush folder (i.e. ~/.drush/drush.ini)
; 3. System wide configuration folder (i.e. /etc/drush/drush.ini)
;
; When in use, the variables defined in this file
; will override the setting values that appear in
; your php.ini file. See the examples below for
; some values that may need to be set in order for
; drush to work.
;
; NOTE: There is a certain amount of overhead
; required for each override, so drush.ini should
; only be used for a relatively small number
; of variables. Comment out any variable that
; has the same value as the webserver php.ini
; to keep the size of the override list small.
;
; To fully specify the value of all php.ini variables,
; copy your webserver php.ini file to one of the
; locations mentioned above (e.g. /etc/drush/php.ini)
; and edit it to suit.
;
; The options listed below are particularly relevant
; to drush.
;
;
; drush needs as much memory as Drupal in order
; to run; make the memory limit setting match
; what you have in your webserver's php.ini.
;
memory_limit = 128M
;
; Show all errors and direct them to stderr
; when running drush.
;
error_reporting = E_ALL | E_NOTICE | E_STRICT
display_errors = stderr
;
; If your php.ini for your webserver is too
; restrictive, you can re-enable functionality
; for drush by adjusting values in this file.
;
; Here are some examples of settings that are
; sometimes set to restrictive values in a
; webserver's php.ini:
;
;safe_mode =
;open_basedir =
;disable_functions =
;disable_classes =

View File

@@ -0,0 +1,230 @@
<?php
/*
* 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.
*
* See `drush topic docs-bootstrap` for more information on how
* bootstrapping affects the loading of drush configuration files.
*/
// 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'] = '.';
// You should not use drush-4.x on Windows; upgrade to the 5.x branch.
// If you are really sure that you want to ignore this advice, you may
// still disable the warning by setting the 'check_os' setting to the
// special value shown below.
# $options['check_os'] = 'i-want-4.x';
// Control automatically check for updates in pm-updatecode and drush version.
// FALSE = never check for updates. 'head' = allow updates to drush-HEAD.
// TRUE (default) = allow updates to latest stable release.
# $options['self-update'] = FALSE;
// By default, drush will download projects compatibile with the
// current version of Drupal, or, if no Drupal site is specified,
// then the Drupal-7 version of the project is downloaded. Set
// default-major to select a different default version.
# $options['default-major'] = 6;
// 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
// Separate by : (Unix-based systems) or ; (Windows).
# $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';
// Specify directory where sql-dump should store backups of database
// dumps. @DATABASE is replaced with the name of the database being
// dumped, and @DATE is replaced with the current time and date of the
// dump. TRUE will cause sql-dump to use the same backup directory that
// pm-updatecode does.
//
// If set, this can be explicitly overridden by specifying --result-file
// on the commandline. The default behavior of dumping to
// STDOUT can be achieved via --result-file=0
# $options['result-file'] = '/path/to/backup/dir/@DATABASE_@DATE.sql';
# $options['result-file'] = TRUE;
// 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';
// rsync version 2.6.8 or earlier will give an error message:
// "--remove-source-files: unknown option". To fix this, set
// $options['rsync-version'] = '2.6.8'; (replace with the lowest
// version of rsync installed on any system you are using with
// drush). Note that drush requires at least rsync version 2.6.4
// for some functions to work correctly.
//
// Note that this option can also be set in a site alias. This
// is preferable if newer versions of rsync are available on some
// of the systems you use.
// See: http://drupal.org/node/955092
# $options['rsync-version'] = '2.6.9';
/*
* 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';
/*
* Multiple command execution options
*/
// By default, drush will prepend the name of the
// site to the output of any multiple-site command
// execution. To disable this behavior, set the
// --no-label option
# $options['no-label'] = TRUE;
/*
* 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
// Separate by : (Unix-based systems) or ; (Windows).
# $command_specific['script']['script-path'] = 'sites/all/scripts:profiles/myprofile/scripts';
// Always show release notes when running pm-update or pm-updatecode
# $command_specific['pm-update'] = array('notes' => TRUE);
# $command_specific['pm-updatecode'] = array('notes' => TRUE);
// List of drush commands or aliases that should override built-in
// shell functions and commands; otherwise, built-ins override drush
// commands. Default is help,dd,sa.
// Warning: bad things can happen if you put the wrong thing here
// (e.g. eval, grep), so be cautious.
// If a drush command overrides a built-in command (e.g. bash help),
// then you can use the `builtin` operator to run the built-in version
// (e.g. `builtin help` to show bash help instead of drush help.)
// If a drush command overrides a shell command (e.g. grep), then
// you can use the regular shell command by typing in the full path
// to the command (e.g. /bin/grep).
# $command_specific['core-cli'] = array('override' => 'help,dd,sa');
/**
* 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',
# );

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env drush
//
// This example demonstrates how to write a drush
// "shebang" script. These scripts start with the
// line "#!/usr/bin/env drush" or "#!/full/path/to/drush".
//
// See `drush topic docs-scripts` for more information.
//
drush_print("Hello world!");
drush_print();
drush_print("The arguments to this command were:");
//
// If called with --everything, use drush_get_arguments
// to print the commandline arguments. Note that this
// call will include 'php-script' (the drush command)
// and the path to this script.
//
if (drush_get_option('everything')) {
drush_print(" " . implode("\n ", drush_get_arguments()));
}
//
// If --everything is not included, then use
// drush_shift to pull off the arguments one at
// a time. drush_shift only returns the user
// commandline arguments, and does not include
// the drush command or the path to this script.
//
else {
while ($arg = drush_shift()) {
drush_print(' ' . $arg);
}
}
drush_print();
//
// We can check which site was bootstrapped via
// the '@self' alias, which is defined only if
// there is a bootstrapped site.
//
$self_record = drush_sitealias_get_record('@self');
if (empty($self_record)) {
drush_print('No bootstrapped site.');
}
else {
drush_print('The following site is bootstrapped:');
_drush_sitealias_print_record($self_record);
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* @file
* Example policy commandfile. Modify as desired.
*
* Validates commands as they are issued and returns an error
* or changes options when policy is violated.
*
* 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.
*/
/**
* Implement of drush_hook_COMMAND_validate().
*
* Prevent catastrophic braino. Note that this file has to be local to the machine
* that intitiates sql-sync command.
*/
function drush_policy_sql_sync_validate($source = NULL, $destination = NULL) {
if ($destination == '@prod') {
return drush_set_error(dt('Per examples/policy.drush.inc, you may never overwrite the production database.'));
}
}
/**
* Implement of drush_hook_COMMAND_validate().
*
* To test this example without copying, execute `drush --include=./examples updatedb`
* from within your drush directory.
*
* Unauthorized users may view pending updates but not execute them.
*/
function drush_policy_updatedb_validate() {
// Check for a token in the request. In this case, we require --token=secret.
if (!drush_get_option('token') == 'secret') {
drush_log(dt('Per site policy, you must add a secret --token complete this command. See examples/policy.drush.inc. If you are running a version of drush prior to 4.3 and are not sure why you are seeing this message, please see http://drupal.org/node/1024824.'), 'warning');
drush_set_context('DRUSH_AFFIRMATIVE', FALSE);
drush_set_context('DRUSH_NEGATIVE', TRUE);
}
}
/**
* Implementation of drush_hook_COMMAND_validate().
*
* Only sudo tells me to make a sandwich: http://xkcd.com/149/
*/
function drush_policy_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.'));
}
}

View File

@@ -0,0 +1,4 @@
I have discovered a truly marvelous proof that it is impossible to
separate a sandwich into two cubes, or four sandwiches into two
fourth of a sandwich, or in general, any sandwich larger than the
second into two like sandwiches. This text file is too narrow to contain it.

View File

@@ -0,0 +1,161 @@
<?php
/**
* @file
* Example drush command.
*
* To run this *fun* command, execute `sudo drush --include=./examples mmas`
* from within your drush directory.
*
* See `drush topic docs-commands` for more information about command authoring.
*
* 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 topic docs-commands` for a list of recognized keys.
*
* @return
* An associative array describing your command(s).
*/
function sandwich_drush_command() {
$items = array();
// The 'make-me-a-sandwich' command
$items['make-me-a-sandwich'] = array(
'description' => "Makes a delicious sandwich.",
'arguments' => array(
'filling' => 'The type of the sandwich (turkey, cheese, etc.)',
),
'options' => array(
'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)',
),
'examples' => array(
'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
),
'aliases' => array('mmas'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
);
// Commandfiles may also add topics. These will appear in
// the list of topics when `drush topic` is executed.
// To view this topic, run `drush --include=/full/path/to/examples topic`
$items['sandwich-exposition'] = array(
'description' => 'Ruminations on the true meaning and philosophy of sandwiches.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
'callback' => 'drush_print_file',
'callback arguments' => array(dirname(__FILE__) . '/sandwich-topic.txt'),
);
return $items;
}
/**
* Implementation of hook_drush_help().
*
* This function is called whenever a drush user calls
* 'drush help <name-of-your-command>'. This hook is optional. If a command
* does not implement this hook, the command's description is used instead.
*
* This hook is also used to look up help metadata, such as help
* category title and summary. See the comments below for a description.
*
* @param
* A string with the help section (prepend with 'drush:')
*
* @return
* A string with the help text for your command.
*/
function sandwich_drush_help($section) {
switch ($section) {
case 'drush:make-me-a-sandwich':
return dt("This command will make you a delicious sandwich, just how you like it.");
// The 'title' meta item is used to name a group of
// commands in `drush help`. If a title is not defined,
// the default is "All commands in ___", with the
// specific name of the commandfile (e.g. sandwich).
// Command files with less than four commands will
// be placed in the "Other commands" section, _unless_
// they define a title. It is therefore preferable
// to not define a title unless the file defines a lot
// of commands.
case 'meta:sandwich:title':
return dt("Sandwich commands");
// The 'summary' meta item is displayed in `drush help --filter`,
// and is used to give a general idea what the commands in this
// command file do, and what they have in common.
case 'meta:sandwich:summary':
return dt("Automates your sandwich-making business workflows.");
}
}
/**
* Implementation of drush_hook_COMMAND_validate().
*
* The validate command should exit with
* `return drush_set_error(...)` to stop execution of
* the command. In practice, calling drush_set_error
* OR returning FALSE is sufficient. See drush.api.php
* for more details.
*/
function drush_sandwich_make_me_a_sandwich_validate() {
$name = posix_getpwuid(posix_geteuid());
if ($name['name'] !== 'root') {
return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
}
}
/**
* Example drush command callback. This is where the action takes place.
*
* The function name should be same as command name but with dashes turned to
* underscores and 'drush_commandfile_' prepended, where 'commandfile' is
* taken from the file 'commandfile.drush.inc', which in this case is 'sandwich'.
* Note also that a simplification step is also done in instances where
* the commandfile name is the same as the beginning of the command name,
* "drush_example_example_foo" is simplified to just "drush_example_foo".
* To also implement a hook that is called before your command, implement
* "drush_hook_pre_example_foo". For a list of all available hooks for a
* given command, run drush in --debug mode.
*
* If for some reason you do not want your hook function to be named
* after your command, you may define a 'callback' item in your command
* object that specifies the exact name of the function that should be
* called. However, the specified callback function must still begin
* with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute")
* if you want that all hook functions are still called (e.g.
* drush_example_pre_foo_execute, and so on).
*
* In this function, all of Drupal's API is (usually) available, including
* any functions you have added in your own modules/themes.
*
* @see drush_invoke()
* @see drush.api.php
*/
function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
$str_spreads = '';
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'));
}

View File

@@ -0,0 +1,24 @@
 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . .:8 ;t;;t;;;;:..:;%SX888@X%t;.. . . . 
 . . .. . . . . . .%t%;%@%%%%%%%%%%X@8888XS%t;...:;ttt%X. .
 . . . . . . . . .X:8%X%%%XS%%%%%%%XS%%%%%@%%%%X%%%@%S88 . 
 . . . . . . . . X@ @%%%X8X%%%88%%%8X%%%%%%%%%XXt@8@88@. .
 . . . . . . . .t@tS;%%8XSX%@XSX%@XSS%8@@88X@888X8SS S;S. 
 . . . . . . .@%XS%%%%%S8@X%@8%XXSSXX%S@SSSX888.;@ 888@ . . 
 . . . . . :.8:S%%%XS8X@@X%S@SSSS8SXSXXX%X88X:;@8@:S  88S. .
 . . . .8%S8%%%%%%8@SSSXXXSXSXSXSXSSS8S888 :@%:%XX:%8%:X: 
 . . .:8 %%%%@%%8@S%%XXSXSSSS8S@X%XSXX88 ;@X;SX88X8;%X88t. 
 . . 88S%S%%%%8XSSXSX@@S@%XS8@SS%@S%888 88@S:8. .;.@%X:@8;. 
 . .  88.8888888@XX 888888%X%@XX 88SS8@@;S@8.%;8@S%%:8  .
 . .  S%:8 @SSSS8 @@8@8 8 88888888@%S:8:S8 @..%S SXX8888;. .
 . . %:8S8888@88SXS S S::X@.8.8 X%S%8X:X88..% @@.S.%% .;. . 
 . .XX8@8;;%%t;;;;:@X@888888@888888.88S;8:8  ... . . .
 . . 8.;;@8@8:%%%%%t.8@%ttX@8@@@S8%8 X8S;X:@; :... . . . . .
 . tS:8@;88.;:8888X8S:.tX88888X88  S8tStS88 :.. . . . . . . 
 .:X;;:t%;tt%888S@8XS888@8.:tt@;88.tXXX8:::... . . . . . . .
 .:X8St:8SXS XS8@X 8.8%888%X8@@X88tXS8t; . . . . . . . . . 
 . :8888.88888888X@@X @ X X%S%;88;8t .. . . . . . . . .
 ... ..: . .@@888%St @ @ 8SS 8:; . . . . . . . . . . .
 . . . ..::. ..:;;::::. ... . . . . . . . . . . . .
 .. . . . . .. . . . . .. . . . . . . . . . . . . . 

View File

@@ -0,0 +1,168 @@
<?php
/**
* @file
* Example drush command.
*
* To run this *fun* command, execute `drush --include=./examples xkcd`
* from within your drush directory.
*
* See `drush topic docs-commands` for more information about command authoring.
*
* 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 topic docs-commands` for a list of recognized keys.
*
* @return
* An associative array describing your command(s).
*/
function xkcd_drush_command() {
$items = array();
// The 'xkcd' command
$items['xkcd-fetch'] = array(
'description' => "Retrieve and display xkcd cartoons.",
'arguments' => array(
'search' => 'Optional argument to retrive the cartoons matching an index number, keyword search or "random". If omitted the latest cartoon will be retrieved.',
),
'options' => array(
'image-viewer' => 'Command to use to view images (e.g. xv, firefox). Defaults to "display" (from ImageMagick).',
'google-custom-search-api-key' => 'Google Custom Search API Key, available from https://code.google.com/apis/console/. Default key limited to 100 queries/day globally.',
),
'examples' => array(
'drush xkcd' => 'Retrieve and display the latest cartoon.',
'drush xkcd sandwich' => 'Retrieve and display cartoons about sandwiches.',
'drush xkcd 123 --image-viewer=eog' => 'Retrieve and display cartoon #123 in eog.',
'drush xkcd random --image-viewer=firefox' => 'Retrieve and display a random cartoon in Firefox.',
),
'aliases' => array('xkcd'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
);
return $items;
}
/**
* Implementation of hook_drush_help().
*
* This function is called whenever a drush user calls
* 'drush help <name-of-your-command>'. This hook is optional. If a command
* does not implement this hook, the command's description is used instead.
*
* This hook is also used to look up help metadata, such as help
* category title and summary. See the comments below for a description.
*
* @param
* A string with the help section (prepend with 'drush:')
*
* @return
* A string with the help text for your command.
*/
function xkcd_drush_help($section) {
switch ($section) {
case 'drush:xkcd-fetch':
return dt("A command line tool (1) for a web site tool (2), that emulates
(badly) a web based tool (3) that emulates (badly) a command line tool (4) to
access a web site (5) with awesome geek humor.\n
(1) Drush
(2) Drupal
(3) http://uni.xkcd.com/
(4) BASH
(5) http://xkcd.com/");
}
}
/**
* Example drush command callback. This is where the action takes place.
*
* The function name should be same as command name but with dashes turned to
* underscores and 'drush_commandfile_' prepended, where 'commandfile' is
* taken from the file 'commandfile.drush.inc', which in this case is 'sandwich'.
* Note also that a simplification step is also done in instances where
* the commandfile name is the same as the beginning of the command name,
* "drush_example_example_foo" is simplified to just "drush_example_foo".
* To also implement a hook that is called before your command, implement
* "drush_hook_pre_example_foo". For a list of all available hooks for a
* given command, run drush in --debug mode.
*
* If for some reason you do not want your hook function to be named
* after your command, you may define a 'callback' item in your command
* object that specifies the exact name of the function that should be
* called. However, the specified callback function must still begin
* with "drush_commandfile_" (e.g. 'callback' => "drush_example_foo_execute")
* if you want that all hook functions are still called (e.g.
* drush_example_pre_foo_execute, and so on).
*
* In this function, all of Drupal's API is (usually) available, including
* any functions you have added in your own modules/themes.
*
* @see drush_invoke()
* @see drush.api.php
*
* @param
* An optional string with search keyworks, cartoon ID or "random".
*/
function drush_xkcd_fetch($search = '') {
if (empty($search)) {
drush_xkcd_display('http://xkcd.com');
}
elseif (is_numeric($search)) {
drush_xkcd_display('http://xkcd.com/' . $search);
}
elseif ($search == 'random') {
$xkcd_response = @json_decode(file_get_contents('http://xkcd.com/info.0.json'));
if (!empty($xkcd_response->num)) {
drush_xkcd_display('http://xkcd.com/' . rand(1, $xkcd_response->num));
}
}
else {
// This uses an API key with a limited number of searches per
$search_response = @json_decode(file_get_contents('https://www.googleapis.com/customsearch/v1?key=' . drush_get_option('google-custom-search-api-key', 'AIzaSyDpE01VDNNT73s6CEeJRdSg5jukoG244ek') . '&cx=012652707207066138651:zudjtuwe28q&q=' . $search));
if (!empty($search_response->items)) {
foreach ($search_response->items as $item) {
drush_xkcd_display($item->link);
}
}
else {
drush_set_error('DRUSH_XKCD_SEARCH_FAIL', dt('The search failed or produced no results.'));
}
}
}
/**
* Retrieve and display a table of metadata for an XKCD cartoon,
* then retrieve and display the cartoon using a specified image viewer.
*
* @param
* A string with the URL of the cartoon to display.
*/
function drush_xkcd_display($url) {
$xkcd_response = @json_decode(file_get_contents($url . '/info.0.json'));
if (!empty($xkcd_response->num)) {
$data = (array)$xkcd_response;
$data['date'] = $data['year'] . '/' . $data['month'] . '/' . $data['day'];
unset($data['safe_title'], $data['news'], $data['link'], $data['year'], $data['month'], $data['day']);
drush_print_table(drush_key_value_to_array_table($data));
$img = realpath(_drush_download_file($data['img']));
drush_register_file_for_deletion($img);
drush_shell_exec(drush_get_option('image-viewer', 'display') . ' ' . $img);
}
else {
drush_set_error('DRUSH_XKCD_METADATA_FAIL', dt('Unable to retrieve cartoon metadata.'));
}
}

View File

@@ -0,0 +1 @@
table.inc

View File

@@ -0,0 +1,656 @@
<?php
/**
* @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. By default, drush is configured to disallow password
* authentication; if you would like to enter a password for every connection,
* then in your drushrc.php file, set $options['ssh-options'] so that it does NOT
* include '-o PasswordAuthentication=no'. See examples/example.drushrc.php.
*
* The results from backend API calls can be fetched via a call to
* drush_backend_get_result().
*/
/**
* 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);
}
}
/**
* Retrieves the results from the last call to backend_invoke.
*
* @returns array
* An associative array containing information from the last
* backend invoke. The keys in the array include:
*
* - output: This item contains the textual output of
* the command that was executed.
* - object: Contains the PHP object representation of the
* result of the command.
* - self: The self object contains the alias record that was
* used to select the bootstrapped site when the command was
* executed.
* - error_status: This item returns the error status for the
* command. Zero means "no error".
* - log: The log item contains an array of log messages from
* the command execution ordered chronologically. Each log
* entery is an associative array. A log entry contains
* following items:
* o type: The type of log entry, such as 'notice' or 'warning'
* o message: The log message
* o timestamp: The time that the message was logged
* o memory: Available memory at the time that the message was logged
* o error: The error code associated with the log message
* (only for log entries whose type is 'error')
* - error_log: The error_log item contains another representation
* of entries from the log. Only log entries whose 'error' item
* is set will appear in the error log. The error log is an
* associative array whose key is the error code, and whose value
* is an array of messages--one message for every log entry with
* the same error code.
* - context: The context item contains a representation of all option
* values that affected the operation of the command, including both
* the command line options, options set in a drushrc.php configuration
* files, and options set from the alias record used with the command.
*/
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();
// If there is a @self record, then include it in the result
$self_record = drush_sitealias_get_record('@self');
if (!empty($self_record)) {
$site_context = drush_get_context('site', array());
unset($site_context['config-file']);
unset($site_context['context-path']);
unset($self_record['loaded-config']);
unset($self_record['#name']);
$data['self'] = array_merge($site_context, $self_record);
}
// Return the options that were set at the end of the process.
$data['context'] = drush_get_merged_options();
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) {
$message = is_array($log['message']) ? implode("\n", $log['message']) : $log['message'];
if (!is_null($log['error'])) {
drush_set_error($log['error'], $message);
}
else {
drush_log($message, $log['type']);
}
}
}
// Output will either be printed, or buffered to the drush_backend_output command.
if (drush_cmp_error('DRUSH_APPLICATION_ERROR') && !empty($data['output'])) {
drush_set_error("DRUSH_APPLICATION_ERROR", dt("Output from failed command :\n !output", array('!output' => $data['output'])));
}
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
);
if (drush_get_context('DRUSH_SIMULATE')) {
drush_print('proc_open: ' . $cmd);
return FALSE;
}
$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.
*
* @deprecated Command name includes arguments, and these are not quote-escaped in any way.
* @see drush_invoke_process("@self", $command, array($arg1, $arg2, ...), $data) for a better option.
*
* @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.
*
* @deprecated; do not call directly.
* @see drush_invoke_process("@self", $command, $args, $data) for a better option.
*/
function drush_backend_invoke_args($command, $args, $data = array(), $method = 'GET', $integrate = TRUE, $drush_path = NULL, $hostname = NULL, $username = NULL, $ssh_options = NULL) {
$cmd = _drush_backend_generate_command($command, $args, $data, $method, $drush_path, $hostname, $username, $ssh_options);
return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $integrate);
}
/**
* Execute a new local or remote command in a new process.
*
* @param site_record
* An array containing information used to generate the command.
* 'remote-host'
* Optional. A remote host to execute the drush command on.
* 'remote-user'
* Optional. Defaults to the current user. If you specify this, you can choose which module to send.
* 'ssh-options'
* Optional. Defaults to "-o PasswordAuthentication=no"
* 'path-aliases'
* Optional; contains paths to folders and executables useful to the command.
* '%drush-script'
* Optional. Defaults to the current drush.php file on the local machine, and
* to simply 'drush' (the drush script in the current PATH) on remote servers.
* You may also specify a different drush.php script explicitly. You will need
* to set this when calling drush on a remote server if 'drush' is not in the
* PATH on that machine.
* @param command
* A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
* @param args
* An array of arguments for the command.
* @param 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.
*
* @return
* A text string representing a fully escaped command.
*
* @deprecated; do not call directly.
* @see drush_invoke_process($site_record, $command, $args, $data) for a better option.
*/
function drush_backend_invoke_sitealias($site_record, $command, $args, $data = array(), $method = 'GET', $integrate = TRUE) {
$cmd = _drush_backend_generate_command_sitealias($site_record, $command, $args, $data, $method);
return _drush_backend_invoke($cmd, $data, array_key_exists('#integrate', $data) ? $data['#integrate'] : $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');
if (array_key_exists('#interactive', $data)) {
drush_log(dt("executing !cmd", array('!cmd' => $cmd)));
return drush_op_system($cmd);
}
else {
$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 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.
*
* @deprecated Is not as flexible as recommended command. @see _drush_backend_generate_command_sitealias().
*/
function _drush_backend_generate_command($command, $args, &$data, $method = 'GET', $drush_path = null, $hostname = null, $username = null, $ssh_options = NULL) {
return _drush_backend_generate_command_sitealias(
array(
'remote-host' => $hostname,
'remote-user' => $username,
'ssh-options' => $ssh_options,
'path-aliases' => array(
'%drush-script' => $drush_path,
),
), $command, $args, $data, $method);
}
/**
* Generate a command to execute.
*
* @param site_record
* An array containing information used to generate the command.
* 'remote-host'
* Optional. A remote host to execute the drush command on.
* 'remote-user'
* Optional. Defaults to the current user. If you specify this, you can choose which module to send.
* 'ssh-options'
* Optional. Defaults to "-o PasswordAuthentication=no"
* 'path-aliases'
* Optional; contains paths to folders and executables useful to the command.
* '%drush-script'
* Optional. Defaults to the current drush.php file on the local machine, and
* to simply 'drush' (the drush script in the current PATH) on remote servers.
* You may also specify a different drush.php script explicitly. You will need
* to set this when calling drush on a remote server if 'drush' is not in the
* PATH on that machine.
* @param command
* A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
* @param args
* An array of arguments for the command.
* @param 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 text string representing a fully escaped command.
*/
function _drush_backend_generate_command_sitealias($site_record, $command, $args, &$data, $method = 'GET') {
$drush_path = null;
$hostname = array_key_exists('remote-host', $site_record) ? $site_record['remote-host'] : null;
$username = array_key_exists('remote-user', $site_record) ? $site_record['remote-user'] : null;
$ssh_options = array_key_exists('ssh-options', $site_record) ? $site_record['ssh-options'] : null;
$os = drush_os($site_record);
$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'];
}
}
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 .= ' ' . drush_escapeshellarg($arg);
}
$interactive = ' ' . (empty($data['#interactive']) ? '' : ' > `tty`') . ' 2>&1';
// @TODO: Implement proper multi platform / multi server support.
$cmd = escapeshellcmd($drush_path) . " " . $option_str . " " . $command . (empty($data['#interactive']) ? " --backend" : "");
if (!is_null($hostname)) {
$username = (!is_null($username)) ? drush_escapeshellarg($username) . "@" : '';
$ssh_options = (!is_null($ssh_options)) ? $ssh_options : drush_get_option('ssh-options', "-o PasswordAuthentication=no");
$cmd = "ssh " . $ssh_options . " " . $username . drush_escapeshellarg($hostname) . " " . drush_escapeshellarg($cmd . ' 2>&1', $os) . $interactive;
}
else {
$cmd .= $interactive;
}
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_op_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;
}
elseif (substr($key,0,1) != '#') {
$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;
}

View File

@@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,597 @@
<?php
/**
* @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.
* cli : Passed as --option=value to the command line.
* stdin : Passed as a JSON encoded string through stdin.
* specific : Defined in a command-specific option record, and
* set in the command context whenever that command is used.
* alias : Defined in an alias record, and set in the
* alias context whenever that alias 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', 'cli', 'stdin', 'specific', 'alias',
'custom', 'site', 'drupal', 'user', 'home.drush', 'system',
'drush', 'default');
return $contexts;
}
/**
* Return a list of possible drushrc file locations.
*
* @context
* A valid drush context from drush_context_names().
* @prefix
* Optional. Specify a prefix to prepend to ".drushrc.php" when looking
* for config files. Most likely used by contrib commands.
* @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, $prefix = NULL) {
$configs = array();
$config_file = $prefix ? $prefix . '.' . 'drushrc.php' : 'drushrc.php';
// 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 . "/" . $config_file;
}
if ($drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT')) {
$configs['drupal'] = $drupal_root . '/' . $config_file;
}
// in the user home directory
if (!is_null(drush_server_home())) {
$configs['user'] = drush_server_home() . '/.' . $config_file;
}
// in $HOME/.drush directory
if (!is_null(drush_server_home())) {
$configs['home.drush'] = drush_server_home() . '/.drush/' . $config_file;
}
// In the system wide configuration folder.
$configs['system'] = drush_get_context('ETC_PREFIX', '') . '/etc/drush/' . $config_file;
// in the drush installation folder
$configs['drush'] = dirname(__FILE__) . '/../' . $config_file;
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.
// Don't run poormanscron during drush request (D7+).
$defaults = array(
'dev_query' => FALSE,
'cron_safe_threshold' => 0,
);
foreach ($defaults as $key => $value) {
// This can be overridden by a command or a drushrc file if needed.
if (!isset($drush_conf_override[$key])) {
$drush_conf_override[$key] = $value;
}
}
/**
* 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.
$special_contexts = drush_get_special_keys();
foreach ($special_contexts as $option_name) {
if (isset($options[$option_name])) {
$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_args, the entire list of arguments will
* be populated. Once the command is dispatched, this will be set to only the remaining
* arguments to the command (i.e. the command name is removed).
*
* @param arguments
* Command line arguments, as an array.
*/
function drush_set_arguments($arguments) {
drush_set_context('arguments', $arguments);
}
/**
* Get the arguments passed to the drush.php script.
*
* When drush_set_arguments is initially called by drush_parse_args,
* 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 and return it as a list. If the
* option in question is passed on the command line, its value should
* be a comma-separated list (e.g. --flag=1,2,3). If the option
* was set in a drushrc.php file, then its value may be either a
* comma-separated list or an array of values (e.g. $option['flag'] = array('1', '2', '3')).
*
* @param option
* The name of the option to get
* @param default
* Optional. The value to return if the option has not been set
* @param context
* Optional. The context to check for the option. If this is set, only this context will be searched.
*/
function drush_get_option_list($option, $default = array(), $context = NULL) {
$result = drush_get_option($option, $default, $context);
if (!is_array($result)) {
$result = explode(',', $result);
}
return $result;
}
/**
* Get the value for an option, but first checks the provided option overrides.
*
* The feature of drush_get_option that allows a list of option names
* to be passed in an array is NOT supported.
*
* @param option_overrides
* An array to check for values before calling drush_get_option.
* @param option
* The name of the option to get.
* @param default
* Optional. The value to return if the option has not been set.
* @param context
* Optional. The context to check for the option. If this is set, only this context will be searched.
*
*/
function drush_get_option_override($option_overrides, $option, $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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
COMMANDS (perhaps keep these notes in $command definition?)
------------
pm-download: GOOD.
pm-updatecode: GOOD.
pm-update
pm-releasenotes
pm-releases
pm-enable: GOOD. testEnDisUnList().
pm-disable: GOOD. testEnDisUnList().
pm-uninstall: GOOD. testEnDisUnList().
pm-list: GOOD. testEnDisUnList().
pm-info:
pm-refresh
version-control: FAIR. See updatecode. To be deprecated all git workflow after git.drupal.org?
package-hander:
sql-cli:
sql-connect:
sql-query:
sql-dump: FAIR. Implicitly tested by siteUpgradeTest.
sql-sync: FAIR. Implicitly tested by siteUpgradeTest.
sql-drop:
updatedb: GOOD. Implicitly tested siteUpgradeTest.
archive-dump: GOOD
archive-restore:
help
version: Good. Implicit by testStandaloneScript()
self-update
php-eval: GOOD. Implicitly tested by many tests (e.g. siteUpgradeTest).
php-script: GOOD.
drupal-directory: GOOD
core-cli: FAIR
cache-clear
core-cron
core-status: FAIR: Implicit test by contextTest.
docs
image
core-rsync
search-*
site-install: FAIR. Implicit test by setUpDrupal().
site-upgrade: GOOD.
test-*
topic
variable-*: GOOD.
watchdog-*
user-*: GOOD.
field-*: GOOD.
INCLUDES
------------
backend: GOOD
batch: FAIR. Implicit by siteUpgradeTest.
command: FAIR
context: FAIR
drush: NONE.
environment
sitealias. FAIR. Explicit test for alias lists. Single aliases implicitly tested by contextTest.
dbtng: Good. Implicit by variable-*.
drupal
exec: GOOD: Implicitly tested all over.
filesystem
output
ROOT
-------------
drush
drush.php
drush.bat

View File

@@ -0,0 +1,44 @@
Drush's test suite based on phpunit (http://www.phpunit.de/).
Usage
--------
- Install PHPUnit [*]
- Optional. Copy phpunit.xml.dist to phpunit.xml and customize if needed.
- From the /tests subdirectory, run `phpunit .` or `runner.php .`
Advanced usage
---------
- Run only tests matching a regex: phpunit --filter=testVersionString .
- XML results: phpunit --filter=testVersionString --log-junit results.xml .
Notes
----------
- I have run tests within Netbeans and it works.
- Speedup downloads with Squid as forward proxy - http://reluctanthacker.rollett.org/node/114.
[*] Install PHPUnit:
Drush requires PHPUnit 3.5 or later; installing with PEAR is easiest.
On Linux:
---------
sudo apt-get install php5-curl php-pear
sudo pear upgrade --force PEAR
sudo pear channel-discover pear.phpunit.de
sudo pear channel-discover components.ez.no
sudo pear channel-discover pear.symfony-project.com
sudo pear install --alldeps phpunit/PHPUnit
On Windows:
-----------
Download and save from go-pear.phar http://pear.php.net/go-pear.phar
php -q go-pear.phar
pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear install --alldeps phpunit/PHPUnit

View File

@@ -0,0 +1,51 @@
<?php
/*
* @file
* Tests for archive.drush.inc
*/
class archiveDumpCase extends Drush_TestCase {
/*
* Test dump and extraction.
*/
public function testArchiveDump() {
$env = 'testarchivedump';
$this->setUpDrupal($env, TRUE, '6');
$root = $this->sites[$env]['root'];
$docroot = 'web';
// Create the alias for D7 site.
$aliases['archivedump'] = array(
'root' => UNISH_SANDBOX . '/' . $docroot,
'uri' => $env,
);
$contents = $this->file_aliases($aliases);
$alias_path = "$root/aliases.drushrc.php";
file_put_contents($alias_path, $contents);
$name = "example";
$dump_dest = "dump.tar.gz";
$options = array(
'root' => $root,
'uri' => $env,
'yes' => NULL,
'destination' => 'dump.tar.gz',
);
$this->drush('archive-dump', array('@archivedump'), $options);
$exec = sprintf('file %s/%s', UNISH_SANDBOX, $dump_dest);
$this->execute($exec);
$output = $this->getOutput();
$expected = UNISH_SANDBOX . "/dump.tar.gz: gzip compressed data, from Unix";
$this->assertEquals($expected, $output);
// Untar it, make sure it looks right.
$exec = sprintf('tar -xzf %s/%s', UNISH_SANDBOX, $dump_dest);
$untar_dest = UNISH_SANDBOX . '/untar';
$exec = sprintf('mkdir %s && cd %s && tar xzf %s/%s', $untar_dest, $untar_dest, UNISH_SANDBOX, $dump_dest);
$this->execute($exec);
$this->execute(sprintf('head %s/unish_%s.sql | grep "MySQL dump"', $untar_dest, $env));
$this->execute('test -f ' . $untar_dest . '/MANIFEST.ini');
$this->execute('test -d ' . $untar_dest . '/' . $docroot);
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* @file
* We choose to test the backend system in two parts.
* - Origin. These tests assure that we are generate a proper ssh command
* when a backend invoke is needed.
* - Target. These tests assure that drush generates a delimited JSON array
* when called with --backend option.
*
* Advantages of this approach:
* - No network calls and thus more robust.
* - No network calls and thus faster.
*/
class backendCase extends Drush_TestCase {
const DRUSH_BACKEND_OUTPUT_DELIMITER = 'DRUSH_BACKEND_OUTPUT_START>>>%s<<<DRUSH_BACKEND_OUTPUT_END';
/*
* Covers the following origin responsibilities.
* - A remote host is recognized in site specification.
* - Generates expected ssh command.
*
* General handling of site aliases will be in sitealiasTest.php.
*/
function testOrigin() {
$exec = sprintf('%s %s version --simulate --ssh-options=%s | grep ssh', self::escapeshellarg(UNISH_DRUSH), self::escapeshellarg('user@server/path/to/drupal#sitename'), self::escapeshellarg('-i mysite_dsa'));
$this->execute($exec);
// $expected might be different on non unix platforms. We shall see.
$expected = "proc_open: ssh -i mysite_dsa 'user'@'server' 'drush --uri='\''sitename'\'' --root='\''/path/to/drupal'\'' --simulate version --backend 2>&1' 2>&1";
$output = $this->getOutput();
$this->assertEquals($expected, $output, 'Expected ssh command was built');
}
/*
* Covers the following target responsibilities.
* - Interpret stdin as options as per REST API.
* - Successfully execute specified command.
* - JSON object has expected contents (including errors).
* - JSON object is wrapped in expected delimiters.
*/
function testTarget() {
$stdin = json_encode(array('filter'=>'sql'));
$exec = sprintf('echo %s | %s help --backend', self::escapeshellarg($stdin), self::escapeshellarg(UNISH_DRUSH));
$this->execute($exec);
$parsed = $this->parse($this->getOutput());
$this->assertTrue((bool) $parsed, 'Successfully parsed backend output');
$this->assertArrayHasKey('log', $parsed);
$this->assertArrayHasKey('output', $parsed);
$this->assertArrayHasKey('object', $parsed);
$this->assertEquals(self::EXIT_SUCCESS, $parsed['error_status']);
// This assertion shows that `help` was called and that stdin options were respected.
$this->assertStringStartsWith('SQL commands', $parsed['output']);
$this->assertEquals('Bootstrap to phase 0.', $parsed['log'][0]['message']);
// Check error propogation by requesting an invalid command (missing Drupal site).
$exec = sprintf('%s core-cron --backend 2>/dev/null', self::escapeshellarg(UNISH_DRUSH));
$this->execute($exec, self::EXIT_ERROR);
$parsed = $this->parse($this->getOutput());
$this->assertEquals(1, $parsed['error_status']);
$this->assertArrayHasKey('DRUSH_NO_DRUPAL_ROOT', $parsed['error_log']);
}
/*
* A slightly less functional copy of drush_backend_parse_output().
*/
function parse($string) {
$regex = sprintf(self::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(self::DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string));
}
if ($output) {
$data = json_decode($output, TRUE);
if (is_array($data)) {
return $data;
}
}
return $string;
}
}

View File

@@ -0,0 +1,56 @@
<?php
class commandCase extends Drush_TestCase {
public function testInvoke() {
$expected = array(
'unit_drush_init',
'drush_unit_invoke_init',
'drush_unit_invoke_validate',
'drush_unit_pre_unit_invoke',
'drush_unit_invoke',
'drush_unit_post_unit_invoke',
'drush_unit_post_unit_invoke_rollback',
'drush_unit_pre_unit_invoke_rollback',
'drush_unit_invoke_validate_rollback',
);
// We expect a return code of 1 so just call execute() directly.
$exec = sprintf('%s unit-invoke --include=%s', UNISH_DRUSH, self::escapeshellarg(dirname(__FILE__)));
$this->execute($exec, self::EXIT_ERROR);
$called = json_decode($this->getOutput());
$this->assertSame($expected, $called);
}
/*
* Assert that $command has interesting properties. Reference command by
* it's alias (dl) to assure that those aliases are built as expected.
*/
public function testGetCommands() {
$eval = '$commands = drush_get_commands();';
$eval .= 'print json_encode($commands[\'dl\'])';
$this->drush('php-eval', array($eval));
$command = json_decode($this->getOutput());
$this->assertEquals('dl', current($command->aliases));
$this->assertEquals('download', current($command->{'deprecated-aliases'}));
$this->assertObjectHasAttribute('version_control', $command->engines);
$this->assertObjectHasAttribute('package_handler', $command->engines);
$this->assertEquals('pm-download', $command->command);
$this->assertEquals('pm', $command->commandfile);
$this->assertEquals('drush_command', $command->callback);
$this->assertObjectHasAttribute('examples', $command->sections);
$this->assertTrue($command->is_alias);
}
/*
* Assert that minimum bootstrap phase is honored.
*
* Not testing dependency on a module since that requires an installed Drupal.
* Too slow for little benefit.
*/
public function testRequirementBootstrapPhase() {
// Assure that core-cron fails when run outside of a Drupal site.
$return = $this->execute(UNISH_DRUSH . ' core-cron --quiet', self::EXIT_ERROR);
}
}

View File

@@ -0,0 +1,150 @@
<?php
/*
* @file
* Assure that context API behaves as designed. Mostly implicitly tested, but we
* do have some edges that need explicit testing.
*
* @see drush/includes/context.inc.
*/
class contextCase extends Drush_TestCase {
function setUpPaths() {
$this->root = $this->sites[$this->env]['root'];
$this->site = $this->root . '/sites/' . $this->env;
$this->home = UNISH_SANDBOX . '/home';
$this->paths = array(
'custom' => UNISH_SANDBOX,
'site' => $this->site,
'drupal' => $this->root,
'user' => $this->home,
'home.drush' => $this->home . '/.drush',
'system' => UNISH_SANDBOX . '/etc/drush',
// We don't want to write a file into drush dir since it is not in the sandbox.
// 'drush' => dirname(realpath(UNISH_DRUSH)),
);
// Run each path through realpath() since the paths we'll compare against
// will have already run through drush_load_config_file().
foreach ($this->paths as $key => $path) $this->paths[$key] = realpath($path);
}
/*
* Try to write a tiny drushrc.php to each place that drush checks. Also
* write a sites/dev/aliases.drushrc.php file to the sandbox.
*/
function setup() {
parent::setUp();
$this->env = 'dev';
$this->setUpDrupal($this->env, FALSE);
$this->setUpPaths();
// These files are only written to sandbox so get automatically cleaned up.
foreach ($this->paths as $key => $path) {
$contents = <<<EOD
<?php
// Written by Drush's contextCase::setup(). This file is safe to delete.
\$options['contextConfig'] = '$key';
\$command_specific['unit-eval']['contextConfig'] = '$key-specific';
EOD;
$path .= $key == 'user' ? '/.drushrc.php' : '/drushrc.php';
if (file_put_contents($path, $contents)) {
$this->written[] = $path;
}
}
// Also write a site alias so we can test its supremacy in context hierarchy.
$path = $this->site . '/aliases.drushrc.php';
$aliases['contextAlias'] = array(
'contextConfig' => 'alias1',
'command-specific' => array (
'unit-eval' => array (
'contextConfig' => 'alias-specific',
),
),
);
$contents = $this->file_aliases($aliases);
$return = file_put_contents($path, $contents);
}
/*
* These should be two different tests but I could not work out how to do that
* without calling setup() twice. setupBeforeClass() did not work out (for MW).
*/
function testContext() {
$this->ConfigFile();
$this->ContextHierarchy();
}
/*
* Assure that all possible config files get loaded.
*/
function ConfigFile() {
$options = array(
'pipe' => NULL,
'config' => UNISH_SANDBOX,
'root' => $this->root,
'uri' => $this->env,
);
$this->drush('core-status', array('Drush configuration'), $options);
$output = trim($this->getOutput());
$loaded = explode(' ', $output);
$this->assertSame($this->written, $loaded);
}
/*
* Assure that options are loaded into right context and hierarchy is
* respected by drush_get_option().
*
* Stdin context not exercised here. See backendCase::testTarget().
*/
function ContextHierarchy() {
// The 'custom' config file has higher priority than cli and regular config files.
$eval = '$contextConfig = drush_get_option("contextConfig", "n/a");';
$eval .= '$cli1 = drush_get_option("cli1");';
$eval .= 'print json_encode(get_defined_vars());';
$config = UNISH_SANDBOX . '/drushrc.php';
$options = array(
'cli1' => NULL,
'config' => $config,
'root' => $this->root,
'uri' => $this->env,
);
$this->drush('php-eval', array($eval), $options);
$output = $this->getOutput();
$actuals = json_decode(trim($output));
$this->assertEquals('custom', $actuals->contextConfig);
$this->assertTrue($actuals->cli1);
// Site alias trumps 'custom'.
$eval = '$contextConfig = drush_get_option("contextConfig", "n/a");';
$eval .= 'print json_encode(get_defined_vars());';
$options = array(
'config' => $config,
'root' => $this->root,
'uri' => $this->env,
);
$this->drush('php-eval', array($eval), $options, '@contextAlias');
$output = $this->getOutput();
$actuals = json_decode(trim($output));
$this->assertEquals('alias1', $actuals->contextConfig);
// Command specific wins over non-specific. If it did not, $expected would
// be 'site'. Note we call unit-eval command in order not to purturb
// php-eval with options in config file.
$eval = '$contextConfig = drush_get_option("contextConfig", "n/a");';
$eval .= 'print json_encode(get_defined_vars());';
$options = array(
'root' => $this->root,
'uri' => $this->env,
'include' => dirname(__FILE__), // Find unit.drush.inc commandfile.
);
$this->drush('unit-eval', array($eval), $options);
$output = $this->getOutput();
$actuals = json_decode(trim($output));
$this->assertEquals('site-specific', $actuals->contextConfig);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* @file
* Tests for core commands.
*/
class coreCase extends Drush_TestCase {
/*
* Test standalone php-script scripts. Assure that script args and options work.
*/
public function testStandaloneScript() {
$this->drush('version', array('drush_version'), array('pipe' => NULL));
$standard = $this->getOutput();
// Write out a hellounish.script into the sandbox. The correct /path/to/drush
// is in the shebang line.
$filename = 'hellounish.script';
$data = '#!/usr/bin/env [PATH-TO-DRUSH]
$arg = drush_shift();
drush_invoke("version", $arg);
';
$data = str_replace('[PATH-TO-DRUSH]', UNISH_DRUSH, $data);
$script = UNISH_SANDBOX . '/' . $filename;
file_put_contents($script, $data);
chmod($script, 0755);
$this->execute("$script drush_version --pipe");
$standalone = $this->getOutput();
$this->assertEquals($standard, $standalone);
}
function testDrupalDirectory() {
$this->setUpDrupal('dev', TRUE);
$root = $this->sites['dev']['root'];
$options = array(
'root' => $root,
'uri' => 'dev',
'verbose' => NULL,
'yes' => NULL,
);
$this->drush('pm-download', array('devel-7.x-1.0'), $options);
$this->drush('pm-enable', array('menu', 'devel'), $options);
$this->drush('drupal-directory', array('devel'), $options);
$output = $this->getOutput();
$this->assertEquals($root . '/sites/all/modules/devel', $output);
$this->drush('drupal-directory', array('%files'), $options);
$output = $this->getOutput();
$this->assertEquals($root . '/sites/dev/files', $output);
$this->drush('drupal-directory', array('%modules'), $options);
$output = $this->getOutput();
$this->assertEquals($root . '/sites/all/modules', $output);
}
function testCoreCLI() {
/*
* @todo
* - BASHRC_PATH. Same file cleanup woes as contextTest.
* - DRUSH_CLI
* - INITIAL_SITE
* - PS1. Hard to test in non interactive session?
* - on
* - use
* - cd, cdd, lsd
* - override, contextual
*/
// Exercise core-cli's interactive mode.
// Include unit.drush.inc commandfile.
$options = array(
'include' => dirname(__FILE__),
);
// These commands will throw a failure if they return non-zero exit code.
// Assure that we create a bash function for command names.
$options['unit-extra'] = 'core-status;exit';
$this->drush('core-cli', array(), $options);
// Assure that we create a bash function for command aliases.
$options['unit-extra'] = 'st;exit';
$this->drush('core-cli', array(), $options);
// Assure that we create a bash alias for site aliases.
// First, write an alias file to the sandbox.
$path = UNISH_SANDBOX . '/aliases.drushrc.php';
$aliases['cliAlias'] = array(
'root' => $this->sites['dev']['root'],
'uri' => 'dev',
);
$contents = $this->file_aliases($aliases);
$return = file_put_contents($path, $contents);
// Append a bash command which starts with alias name (i.e. @cliAlias).
$options['unit-extra'] = sprintf('@cliAlias core-status --alias-path=%s;exit', UNISH_SANDBOX);
$options['alias-path'] = UNISH_SANDBOX;
$this->drush('core-cli', array(), $options);
// $this->markTestIncomplete('In progress below.');
// Exercise core-cli's non-interactive mode.
// We spawn our own bash session using the --pipe feature of core-cli.
//$options = array(
// 'pipe' => NULL,
// 'alias-path' => UNISH_SANDBOX,
//);
//$this->drush('core-cli', array(), $options);
//$bashrc_data = $this->getOutput();
//$bashrc_file = UNISH_SANDBOX . '/.bashrc';
//$extra = 'cd @cliAlias;exit;';
//$return = file_put_contents($bashrc_file, $bashrc_data . $extra);
//$this->setUpDrupal('dev', FALSE);
//$this->execute('bash --rcfile ' . $bashrc_file);
//$output = $this->getOutput();
//$this->assertContains('????', $output);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
<?php
/*
* @file
* Initialize a sandboxed environment. Starts with call unish_init() at bottom.
*/
abstract class Drush_TestCase extends PHPUnit_Framework_TestCase {
// Unix exit codes.
const EXIT_SUCCESS = 0;
const EXIT_ERROR = 1;
/*
* An array of Drupal sites that are setup in the drush-sandbox.
*/
var $sites;
function __construct() {
$this->_output = false;
}
/**
* Assure that each class starts with an empty sandbox directory and
* a clean environment - http://drupal.org/node/1103568.
*/
public static function setUpBeforeClass() {
$sandbox = UNISH_SANDBOX;
if (file_exists($sandbox)) {
unish_file_delete_recursive($sandbox);
}
$ret = mkdir($sandbox, 0777, TRUE);
chdir(UNISH_SANDBOX);
mkdir(getenv('HOME') . '/.drush', 0777, TRUE);
mkdir($sandbox . '/etc/drush', 0777, TRUE);
mkdir($sandbox . '/share/drush/commands', 0777, TRUE);
}
/**
* Runs after each test case. Remove sandbox directory.
*/
public static function tearDownAfterClass() {
if (file_exists(UNISH_SANDBOX)) {
unish_file_delete_recursive(UNISH_SANDBOX);
}
}
public static function is_windows() {
return (strtoupper(substr(PHP_OS, 0, 3)) == "WIN");
}
public static function escapeshellarg($arg) {
// Short-circuit escaping for simple params (keep stuff readable)
if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) {
return $arg;
}
elseif (self::is_windows()) {
return self::_escapeshellarg_windows($arg);
}
else {
return escapeshellarg($arg);
}
}
public static function _escapeshellarg_windows($arg) {
// Double up existing backslashes
$arg = preg_replace('/\\\/', '\\\\\\\\', $arg);
// Escape double quotes.
$arg = preg_replace('/"/', '\\"', $arg);
// Escape single quotes.
$arg = preg_replace('/\'/', '\\\'', $arg);
// Add surrounding quotes.
$arg = '"' . $arg . '"';
return $arg;
}
/**
* Actually runs the command. Does not trap the error stream output as this
* need PHP 4.3+.
*
* @param string $command
* The actual command line to run.
* @return integer
* Exit code. Usually self::EXIT_ERROR or self::EXIT_SUCCESS.
*/
function execute($command, $expected_return = self::EXIT_SUCCESS) {
$this->_output = FALSE;
// todo check verbose level from phpunit.
if (TRUE) {
print "\nExecuting: $command \n";
}
exec($command, $this->_output, $return);
$this->assertEquals($expected_return, $return, 'Unexpected exit code: ' . $command);
return $return;
}
/**
* Invoke drush in via execute().
*
* @param command
* A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
* @param args
* Command arguments.
* @param $options
* An associative array containing options.
* @param $site_specification
* A site alias or site specification. Include the '@' at start of a site alias.
* @param $cd
* A directory to change into before executing.
* @return integer
* An exit code.
*/
function drush($command, array $args = array(), array $options = array(), $site_specification = NULL, $cd = NULL) {
$cmd[] = $cd ? sprintf('cd %s;', self::escapeshellarg($cd)) : NULL;
$cmd[] = UNISH_DRUSH;
$cmd[] = empty($site_specification) ? NULL : self::escapeshellarg($site_specification);
$cmd[] = $command;
if (in_array('--verbose', $_SERVER['argv'])) $args[] = '--verbose';
if (in_array('--debug', $_SERVER['argv'])) $args[] = '--debug';
foreach ($args as $arg) {
$cmd[] = self::escapeshellarg($arg);
}
$options['nocolor'] = NULL;
foreach ($options as $key => $value) {
if (is_null($value)) {
$cmd[] = "--$key";
}
else {
$cmd[] = "--$key=" . self::escapeshellarg($value);
}
}
$exec = array_filter($cmd, 'strlen'); // Remove NULLs
return $this->execute(implode(' ', $exec));
}
/**
* Accessor for the last output.
* @return string Output as text.
* @access public
*/
function getOutput() {
return implode("\n", $this->_output);
}
/**
* Accessor for the last output.
* @return array Output as array of lines.
* @access public
*/
function getOutputAsList() {
return $this->_output;
}
function setUpDrupal($env = 'dev', $install = FALSE, $version_string = '7.x', $profile = NULL) {
$root = UNISH_SANDBOX . '/web';
$this->sites[$env]['root'] = $root;
$site = "$root/sites/$env";
if (is_null($profile)) {
$profile = substr($version_string, 0, 1) >= 7 ? 'testing' : 'default';
}
// Download Drupal if not already present.
if (!file_exists($root)) {
$options = array(
'destination' => UNISH_SANDBOX,
'drupal-project-rename' => 'web',
'yes' => NULL,
'quiet' => NULL,
);
$this->drush('pm-download', array("drupal-$version_string"), $options);
}
// If specified, install Drupal as a multi-site.
if ($install) {
$options = array(
'root' => $root,
'db-url' => UNISH_DB_URL . '/unish_' . $env,
'sites-subdir' => $env,
'yes' => NULL,
'quiet' => NULL,
);
$this->drush('site-install', array($profile), $options);
// Give us our write perms back.
$ret = chmod($site, 0777);
// Stash the db_url for this site.
$this->sites[$env]['db_url'] = UNISH_DB_URL . '/unish_' . $env;
}
else {
mkdir($site);
touch("$site/settings.php");
}
// Make an alias for the site
$alias_definition = array($env => array('root' => $root, 'uri' => $env));
file_put_contents(UNISH_SANDBOX . '/etc/drush/' . $env . '.alias.drushrc.php', $this->file_aliases($alias_definition));
}
// Copied from D7 - profiles/standard/standard.install
function create_node_types_php() {
$php = "
\$types = array(
array(
'type' => 'page',
'name' => 'Basic page',
'base' => 'node_content',
'description' => 'Use <em>basic pages</em> for your static content, such as an \'About us\' page.',
'custom' => 1,
'modified' => 1,
'locked' => 0,
),
array(
'type' => 'article',
'name' => 'Article',
'base' => 'node_content',
'description' => 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.',
'custom' => 1,
'modified' => 1,
'locked' => 0,
),
);
foreach (\$types as \$type) {
\$type = node_type_set_defaults(\$type);
node_type_save(\$type);
node_add_body_field(\$type);
}
";
return $php;
}
/*
* Prepare the contents of an aliases file.
*/
function file_aliases($aliases) {
foreach ($aliases as $name => $alias) {
$records[] = sprintf('$aliases[\'%s\'] = %s;', $name, var_export($alias, TRUE));
}
$contents = "<?php\n\n" . implode("\n\n", $records);
return $contents;
}
/**
* Same code as drush_delete_dir().
* @see drush_delete_dir()
*
* @param string $dir
* @return boolean
*/
function file_delete_recursive($dir) {
if (!file_exists($dir)) {
return TRUE;
}
if (!is_dir($dir)) {
@chmod($dir, 0777); // Make file writeable
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!self::file_delete_recursive($dir.'/'.$item)) {
return FALSE;
}
}
return rmdir($dir);
}
}
/*
* Initialize our environment at he start of each run (i.e. suite).
*/
function unish_init() {
// We read from globals here because env can be empty and ini did not work in quick test.
define('UNISH_DB_URL', getenv('UNISH_DB_URL') ? getenv('UNISH_DB_URL') : !empty($GLOBALS['UNISH_DB_URL']) ? $GLOBALS['UNISH_DB_URL'] : 'mysql://root:@127.0.0.1');
// UNISH_DRUSH value can come from phpunit.xml or `which drush`.
if (!defined('UNISH_DRUSH')) {
// Let the UNISH_DRUSH environment variable override if set.
$unish_drush = isset($_SERVER['UNISH_DRUSH']) ? $_SERVER['UNISH_DRUSH'] : NULL;
$unish_drush = isset($GLOBALS['UNISH_DRUSH']) ? $GLOBALS['UNISH_DRUSH'] : $unish_drush;
if (empty($unish_drush)) {
$unish_drush = Drush_TestCase::is_windows() ? exec('for %i in (drush) do @echo. %~$PATH:i') : trim(`which drush`);
}
define('UNISH_DRUSH', $unish_drush);
}
define('UNISH_TMP', getenv('UNISH_TMP') ? getenv('UNISH_TMP') : (isset($GLOBALS['UNISH_TMP']) ? $GLOBALS['UNISH_TMP'] : sys_get_temp_dir()));
define('UNISH_SANDBOX', UNISH_TMP . '/drush-sandbox');
$home = UNISH_SANDBOX . '/home';
putenv("HOME=$home");
putenv("HOMEDRIVE=$home");
putenv('ETC_PREFIX=' . UNISH_SANDBOX);
putenv('SHARE_PREFIX=' . UNISH_SANDBOX);
// Cache dir lives outside the sandbox so that we get persistence across classes.
$cache = UNISH_TMP . '/drush_cache';
putenv("CACHE_PREFIX=" . $cache);
// Wipe at beginning of run.
if (file_exists($cache)) {
unish_file_delete_recursive($cache);
}
}
/**
* Same code as drush_delete_dir().
* @see drush_delete_dir()
*
* @param string $dir
* @return boolean
*/
function unish_file_delete_recursive($dir) {
if (!file_exists($dir)) {
return TRUE;
}
if (!is_dir($dir)) {
@chmod($dir, 0777); // Make file writeable
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!unish_file_delete_recursive($dir.'/'.$item)) {
return FALSE;
}
}
return rmdir($dir);
}
// This code is in global scope.
// TODO: I would rather this code at top of file, but I get Fatal error: Class 'Drush_TestCase' not found
unish_init();

View File

@@ -0,0 +1,61 @@
<?php
/*
* @file
* Tests for field.drush.inc
*/
class fieldCase extends Drush_TestCase {
public function testField() {
$this->setUpDrupal('dev', TRUE);
$options = array(
'yes' => NULL,
'root' => $this->sites['dev']['root'],
'uri' => 'dev',
);
// Create two field instances on article content type.
$this->drush('field-create', array('user', 'city,text,text_textfield', 'subtitle,text,text_textfield'), $options + array('entity_type' => 'user'));
$output = $this->getOutput();
list($city, $subtitle) = explode(' ', $output);
$url = parse_url($subtitle);
$this->assertEquals('/admin/config/people/accounts/fields/subtitle', $url['path']);
// Assure that the second field instance was created correctly (subtitle).
$this->verifyInstance('subtitle', $options);
// Assure that field update URL looks correct.
$this->drush('field-update', array('subtitle'), $options);
$output = $this->getOutput();
$url = parse_url($this->getOutput());
$this->assertEquals('/admin/config/people/accounts/fields/subtitle', $url['path']);
// Assure that field-clone actually clones.
$this->drush('field-clone', array('subtitle', 'subtitlecloned'), $options);
$this->verifyInstance('subtitlecloned', $options);
// Assure that delete works.
$this->drush('field-delete', array('subtitlecloned'), $options);
$this->verifyInstance('subtitlecloned', $options, FALSE);
}
function verifyInstance($name, $options, $expected = TRUE) {
$this->drush('field-info', array('fields'), $options + array('pipe' => NULL));
$output = $this->getOutputAsList();
$found = FALSE;
foreach($output as $row) {
$columns = explode(',', $row);
if ($columns[0] == $name) {
$this->assertEquals('text', $columns[1], $name . ' field is of type=text.');
$this->assertEquals('user', $columns[2], $name . ' field was added to user bundle.');
$found = TRUE;
break;
}
}
if ($expected) {
$this->assertTrue($found, $name . ' field was created.');
}
else {
$this->assertFalse($found, $name . ' field was not present.');
}
}
}

View File

@@ -0,0 +1,23 @@
<!-- Copy and rename to phpunit.xml. Customize as needed. -->
<phpunit backupGlobals="false"
backupStaticAttributes="false"
syntaxCheck="false"
bootstrap="drush_testcase.inc">
<php>
<!-- These variables may alternatively be set as bash environment variables. -->
<!--Uncomment and edit one of the lines below depending on DB platform.-->
<!--DB User must have create/drop permissions-->
<!-- <var name="UNISH_DB_URL" value="mysql://root:@127.0.0.1"/> -->
<!-- <var name="UNISH_DB_URL" value="pgsql://postgres:@localhost"/>-->
<!--User must have write permissions to this directory.-->
<!--If not supplied, defaults to sys_get_tmp_dir().-->
<!-- <var name="UNISH_TMP" value="/tmp"/> -->
<!--Uncomment the line below if your path to drush differs from `which drush`. Use absolute path.-->
<!--<var name="UNISH_DRUSH" value="/Users/mw/bin/drush"/>-->
<includePath>.</includePath>
</php>
</phpunit>

View File

@@ -0,0 +1,78 @@
<?php
/**
* pm-download testing
*/
class pmDownloadCase extends Drush_TestCase {
public function testPmDownload() {
$this->drush('pm-download', array('devel'));
$this->assertFileExists(UNISH_SANDBOX . '/devel/README.txt');
}
/*
* Parse Drupal version and release from command argument.
*
* --dev option bypasses the logic tested here.
*
* @see pm_parse_project_version().
*/
public function testVersionString() {
$eval = 'print json_encode(pm_parse_project_version(array("devel-6.x-1.18")));';
$this->drush('php-eval', array($eval));
$request_data = json_decode($this->getOutput());
$this->assertObjectHasAttribute('devel', $request_data);
$this->assertEquals($request_data->devel->drupal_version, '6.x');
$this->assertEquals($request_data->devel->project_version, '1.18');
}
/*
* Pick right release from the XML (dev, latest published+recommended, ...).
*/
public function testReleaseXML() {
// Use a local, static XML file because live files change over time.
$xml = dirname(__FILE__). '/devel.xml';
// Pick specific release.
$request_data = array(
'name' => 'devel',
'drupal_version' => '6.x',
'project_version' => '1.18',
'version' => '6.x-1.18',
);
// Build an $eval string for use with php-eval in a subprocess.
$eval = '$request_data = ' . var_export($request_data, TRUE) . ";\n";
$eval .= '$release = pm_parse_release($request_data, simplexml_load_file(\'' . $xml . "'));\n";
$eval .= 'print json_encode($release);';
$this->drush('php-eval', array($eval));
$release = json_decode($this->getOutput());
$this->assertEquals($release->version, '6.x-1.18');
// Pick latest recommended+published with no further specification.
// 6.x-2.2 is skipped because it is unpublished.
// 6.x-2.2-rc1 is skipped because it is not a stable release.
// Remove unwanted $request_data items.
$eval = str_replace(array("'project_version' => '1.18',\n", "'version' => '6.x-1.18',\n"), NULL, $eval);
$this->drush('php-eval', array($eval));
$release = json_decode($this->getOutput());
$this->assertEquals($release->version, '6.x-2.1');
}
// @todo Test pure drush commandfile projects. They get special destination.
public function testDestination() {
// Setup first Drupal site. Skip install for speed.
$this->setUpDrupal('dev', FALSE);
$root = $this->sites['dev']['root'];
// Default to sites/all
$this->drush('pm-download', array('devel'), array('root' => $root));
$this->assertFileExists($root . '/sites/all/modules/devel/README.txt');
// If we are in site specific dir, then download belongs there.
// Setup a second site. Skip install for speed.
$this->setUpDrupal('stage', FALSE);
$path_stage = "$root/sites/stage";
mkdir("$path_stage/modules");
$this->drush('pm-download', array('devel'), array(), NULL, $path_stage);
$this->assertFileExists($path_stage . '/modules/devel/README.txt');
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* @file
* Tests for enable, disable, uninstall, pm-list commands.
*/
class EnDisUnListCase extends Drush_TestCase {
public function testEnDisUnList() {
$this->setUpDrupal('dev', TRUE);
$options = array(
'yes' => NULL,
'pipe' => NULL,
'root' => $this->sites['dev']['root'],
'uri' => 'dev',
);
$this->drush('pm-download', array('devel-7.x-1.0'), $options);
$this->drush('pm-list', array(), $options + array('no-core' => NULL, 'status' => 'not installed'));
$list = $this->getOutputAsList();
$this->assertTrue(in_array('devel', $list));
$this->drush('pm-enable', array('menu', 'devel'), $options);
$this->drush('pm-list', array(), $options + array('status' => 'enabled'));
$list = $this->getOutputAsList();
$this->assertTrue(in_array('devel', $list));
$this->assertTrue(in_array('bartik', $list), 'Themes are in the pm-list');
$this->drush('pm-list', array(), $options + array('package' => 'Core'));
$list = $this->getOutputAsList();
$this->assertFalse(in_array('devel', $list), 'Devel is not part of core package');
// For testing uninstall later.
$this->drush('variable-set', array('devel_query_display', 1), $options);
$this->drush('pm-disable', array('devel'), $options);
$this->drush('pm-list', array(), $options + array('status' => 'disabled'));
$list = $this->getOutputAsList();
$this->assertTrue(in_array('devel', $list));
$this->drush('pm-uninstall', array('devel'), $options);
$this->drush('pm-list', array(), $options + array('status' => 'not installed', 'type' => 'module'));
$list = $this->getOutputAsList();
$this->assertTrue(in_array('devel', $list));
// We expect an exit code of 1 so just call execute() directly.
$exec = sprintf('%s variable-get %s --pipe --root=%s --uri=%s', UNISH_DRUSH, 'devel_query_display', $options['root'], $options['uri']);
$this->execute($exec, self::EXIT_ERROR);
$output = $this->getOutput();
$this->assertEmpty($output, 'Devel variable was uninstalled.');
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @file
* Prepare a codebase and upgrade it in several stages, exercising
* updatecode's filters.
* @todo test security-only once one of these modules or core gets a security release.
*/
class pmUpdateCode extends Drush_TestCase {
/*
* Download old core and older contrib releases which will always need updating.
*/
public function setUp() {
$this->setUpDrupal('dev', TRUE, '7.0-rc3');
$options = array(
'root' => $this->sites['dev']['root'],
'uri' => 'dev',
'yes' => NULL,
'quiet' => NULL,
);
$this->drush('pm-download', array('devel-7.x-1.0-rc1,webform-7.x-3.4-beta1'), $options);
$this->drush('pm-enable', array('menu', 'devel', 'webform'), $options);
}
function testUpdateCode() {
$options = array(
'root' => $this->sites['dev']['root'],
'uri' => 'dev',
'yes' => NULL,
'backup-dir' => UNISH_SANDBOX . '/backups',
'self-update' => 0, // Don't try update Drush.
);
// Try to upgrade a specific module.
$this->drush('pm-updatecode', array('devel'), $options + array());
// Assure that devel was upgraded and webform was not.
$this->drush('pm-updatecode', array(), $options + array('pipe' => NULL));
$all = $this->getOutput();
$this->assertNotContains('devel', $all);
$this->assertContains('webform', $all);
// Lock webform, and update core.
$this->drush('pm-updatecode', array(), $options + array('lock' => 'webform'));
$list = $this->getOutputAsList(); // For debugging.
$this->drush('pm-updatecode', array(), $options + array('pipe' => NULL));
$all = $this->getOutput();
$this->assertNotContains('drupal', $all, 'Core was updated');
$this->assertContains('webform', $all, 'Webform was skipped.');
// Unlock webform, update, and check.
$this->drush('pm-updatecode', array(), $options + array('unlock' => 'webform', 'no-backup' => NULL));
$list = $this->getOutputAsList();
$this->drush('pm-updatecode', array(), $options + array('pipe' => NULL));
$all = $this->getOutput();
$this->assertNotContains('webform', $all, 'Webform was updated');
// Verify that we keep backups as instructed.
$pattern = 'find %s -iname %s';
$backup_dir = UNISH_SANDBOX . '/backups';
$cmd = sprintf($pattern, self::escapeshellarg($backup_dir), escapeshellarg('devel.module'));
$this->execute($cmd);
$output = $this->getOutput();
$this->assertNotEmpty($output);
$cmd = sprintf($pattern, self::escapeshellarg($backup_dir), escapeshellarg('webform.module'));
$this->execute($cmd);
$output = $this->getOutput();
$this->assertEmpty($output);
}
}

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env php
<?php
/**
* @file
* A nearly verbatim copy of phpunit script that ships with PEAR's PHPUnit.
*/
require_once 'PHP/CodeCoverage/Filter.php';
PHP_CodeCoverage_Filter::getInstance()->addFileToBlacklist(__FILE__, 'PHPUNIT');
if (extension_loaded('xdebug')) {
// Drush comments out the following line for easier debugging.
// xdebug_disable();
}
if (strpos('/usr/bin/php', '@php_bin') === 0) {
set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
}
require_once 'PHPUnit/Autoload.php';
define('PHPUnit_MAIN_METHOD', 'PHPUnit_TextUI_Command::main');
PHPUnit_TextUI_Command::main();

View File

@@ -0,0 +1,30 @@
<?php
/*
* @file
* Tests for sitealias.inc
*/
class saCase extends Drush_TestCase {
/*
* Assure that site lists work as expected.
* @todo Use --backend for structured return data. Depends on http://drupal.org/node/1043922
*/
public function testSAList() {
$this->setUpDrupal('dev');
$this->setUpDrupal('stage');
$eval = 'print "bon";';
$options = array(
'yes' => NULL,
'root' => $this->sites['dev']['root'],
);
$this->drush('php-eval', array($eval), $options, "#dev,#stage");
$expected = "You are about to execute 'php-eval print \"bon\";' on all of the following targets:
#dev
#stage
Continue? (y/n): y
#dev >> bon
#stage >> bon";
$this->assertEquals($expected, $this->getOutput());
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* @file
* Programmatically upgrade a site from Drupal 6 to Druapl 7.
*
* We also implicitly test:
* - pm-download
* - site-install for D6
* - user-create
* - sql-sync
* - updatedb and batch.inc
*/
class siteUpgradeCase extends Drush_TestCase {
function testUpgrade() {
$env = 'testupgrade';
$this->setUpDrupal($env, TRUE, '6.x');
$root = $this->sites[$env]['root'];
// Create the alias for D7 site.
$aliases['target'] = array(
'root' => UNISH_SANDBOX . '/target',
'uri' => $env,
'db-url' => UNISH_DB_URL . '/unish_target',
);
$contents = $this->file_aliases($aliases);
$alias_path = "$root/aliases.drushrc.php";
file_put_contents($alias_path, $contents);
// Create a user in D6.
$name = "example";
$options = array(
'mail' => "example@example.com",
'password' => 'password',
'root' => $root,
'uri' => $env,
);
$this->drush('user-create', array($name), $options);
// Perform the upgrade.
$options = array(
'yes' => NULL,
'root' => $root,
'uri' => $env,
);
$this->drush('site-upgrade', array('@target'), $options);
// Assert that the D7 site bootstraps.
// We don't specify @target alias since that file is in the root of the *source* site.
$options = array(
'pipe' => NULL,
'root' => $aliases['target']['root'],
'uri' => $aliases['target']['uri'],
);
$return = $this->drush('core-status', array('drupal_bootstrap'), $options);
$this->assertEquals('Successful', $this->getOutput(), 'The target site bootstraps successfully');
// Assures that a updatedb and batch updates work properly. See user_update_7001().
$options = array(
'root' => $aliases['target']['root'],
'uri' => $aliases['target']['uri'],
);
$eval = "require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');";
$eval .= "\$account = user_load_by_name('example');";
$eval .= "print (string) user_check_password('password', \$account)";
$this->drush('php-eval', array($eval), $options);
$output = $this->getOutput();
$this->assertSame('1', $output, 'User was updated to new password format.');
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* @file
* For now we only test sql-sync in simulated mode.
*
* Future: Using two copies of Drupal, we could test
* overwriting one site with another.
*/
class sqlSyncTest extends Drush_TestCase {
/*
* Covers the following responsibilities.
* - A user created on the source site is copied to the destination site.
* - The email address of the copied user is sanitized on the destination site.
*
* General handling of site aliases will be in sitealiasTest.php.
*/
public function testLocalSqlSync() {
$this->setUpDrupal('dev', TRUE);
$this->setUpDrupal('stage', TRUE);
$dump_dir = UNISH_SANDBOX . "/dump-dir";
mkdir($dump_dir);
// Create a user in the staging site
$name = 'joe.user';
$mail = "joe.user@myhome.com";
$options = array(
'root' => $this->sites['stage']['root'],
'uri' => 'stage',
'yes' => NULL,
);
$this->drush('user-create', array($name), $options + array('password' => 'password', 'mail' => $mail));
// Copy stage to dev with --sanitize
$sync_options = array(
'sanitize' => NULL,
'yes' => NULL,
'dump-dir' => $dump_dir
);
$this->drush('sql-sync', array('@stage', '@dev'), $sync_options);
// Confirm that the sample user has the correct email address on the staging site
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$uid = $row[1];
$this->assertEquals($mail, $row[2], 'email address is unchanged on source site.');
$this->assertEquals($name, $row[0]);
$options = array(
'root' => $this->sites['dev']['root'],
'uri' => 'dev',
'yes' => NULL,
);
// Confirm that the sample user's email address has been sanitized on the dev site
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$uid = $row[1];
$this->assertEquals("user+2@localhost", $row[2], 'email address was sanitized on destination site.');
$this->assertEquals($name, $row[0]);
}
}

View File

@@ -0,0 +1,76 @@
<?php
// $Id$
/*
* @file
* Commands which are useful for unit tests.
*/
/**
* Implementation of hook_drush_command().
*/
function unit_drush_command() {
$items['unit-eval'] = array(
'description' => 'Works like php-eval. Used for testing $command_specific context.',
'bootstrap' => DRUSH_BOOTSTRAP_MAX,
'callback' => 'drush_core_php_eval', // Note - no invoke hooks.
);
$items['unit-invoke'] = array(
'description' => 'Return an array indicating which invoke hooks got called.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
);
return $items;
}
/*
* Dynamically append custom bash code to the generated .bashrc.
*
* @see coreCase::testCoreCLI().
*/
function unit_cli_bashrc($drush_command, $interactive_mode) {
return drush_get_option('unit-extra');
}
// Implement each invoke hook with the same single line of code.
// That line records that the hook was called.
function drush_unit_invoke_init() {unit_invoke_log(__FUNCTION__);}
function drush_unit_invoke_validate() {unit_invoke_log(__FUNCTION__);}
function drush_unit_pre_unit_invoke() {unit_invoke_log(__FUNCTION__);}
function drush_unit_invoke() {unit_invoke_log(__FUNCTION__);}
function drush_unit_pre_unit_invoke_rollback() {unit_invoke_log(__FUNCTION__);}
function drush_unit_post_unit_invoke_rollback() {unit_invoke_log(__FUNCTION__);}
// Record that hook_drush_init() fired.
function unit_drush_init() {
$command = drush_get_command();
if ($command['command'] == 'unit-invoke') {
unit_invoke_log(__FUNCTION__);
}
}
function drush_unit_post_unit_invoke() {
// Record that this hook was called.
unit_invoke_log(__FUNCTION__);
// Make sure we enter into rollback.
drush_set_error('');
}
/*
* The final invoke hook. Emit the call history.
* Cannot use 'exit' as it does not fire in rollback scenario.
*/
function drush_unit_invoke_validate_rollback() {
unit_invoke_log(__FUNCTION__);
print json_encode(unit_invoke_log());
}
function unit_invoke_log($function = NULL) {
static $called = array();
if ($function) {
$called[] = $function;
}
else {
return $called;
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* @file
* Tests for user.drush.inc
*/
class userCase extends Drush_TestCase {
/*
* Create, edit, block, and cancel users.
*/
public function testUser() {
// user-create
$env = 'dev';
$this->setUpDrupal($env, TRUE);
$root = $this->sites[$env]['root'];
$name = "example";
$options = array(
'root' => $root,
'uri' => $env,
'yes' => NULL,
);
$this->drush('user-create', array($name), $options + array('password' => 'password', 'mail' => "example@example.com"));
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$uid = $row[1];
$this->assertEquals('example@example.com', $row[2]);
$this->assertEquals($name, $row[0]);
$this->assertEquals(1, $row[3], 'Newly created user is Active.');
$this->assertEquals('authenticated user', $row[4], 'Newly created user has one role.');
// user-block
$this->drush('user-block', array($name), $options);
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$this->assertEquals(0, $row[3], 'User is blocked.');
// user-unblock
$this->drush('user-unblock', array($name), $options);
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$this->assertEquals(1, $row[3], 'User is unblocked.');
// user-add-role
// first, create the fole since we use testing install profile.
$eval = "user_role_save((object)array('name' => 'administrator'))";
$this->drush('php-eval', array($eval), $options);
$this->drush('user-add-role', array('administrator', $name), $options);
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$this->assertEquals('authenticated user, administrator', $row[4], 'User has administrator role.');
// user-remove-role
$this->drush('user-remove-role', array('administrator', $name), $options);
$this->drush('user-information', array($name), $options + array('pipe' => NULL));
$output = $this->getOutput();
$row = str_getcsv($output);
$this->assertEquals('authenticated user', $row[4], 'User removed administrator role.');
// user-password
$newpass = 'newpass';
$this->drush('user-password', array($name), $options + array('password' => $newpass));
$eval = "require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');";
$eval .= "\$account = user_load_by_name('example');";
$eval .= "print (string) user_check_password('$newpass', \$account)";
$this->drush('php-eval', array($eval), $options);
$output = $this->getOutput();
$this->assertEquals('1', $output, 'User can login with new password.');
// user-login
$this->drush('user-login', array($name), $options);
$output = $this->getOutput();
$url = parse_url($output);
$this->assertStringStartsWith('/user/reset/' . $uid, $url['path'], 'Login returned a valid reset URL');
// user-cancel
// create content
$eval = $this->create_node_types_php();
$this->drush('php-eval', array($eval), $options);
$eval = "
\$node = (object) array(
'title' => 'foo',
'uid' => 2,
'type' => 'page',
);
node_save(\$node);
";
$this->drush('php-eval', array($eval), $options);
$this->drush('user-cancel', array($name), $options + array('delete-content' => NULL));
$eval = 'print (string) user_load(2)';
$this->drush('php-eval', array($eval), $options);
$output = $this->getOutput();
$this->assertEmpty($output, 'User was deleted');
$eval = 'print (string) node_load(2)';
$this->drush('php-eval', array($eval), $options);
$output = $this->getOutput();
$this->assertEmpty($output, 'Content was deleted');
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* @file
* Tests for enable, disable, uninstall, pm-list commands.
*/
class VariableCase extends Drush_TestCase {
function testVariable() {
$env = 'dev';
$this->setUpDrupal($env, TRUE);
$options = array(
'yes' => NULL,
'pipe' => NULL,
'root' => $this->sites[$env]['root'],
'uri' => $env,
);
$this->drush('variable-set', array('date_default_timezone', 'US/Mountain'), $options);
$this->drush('variable-get', array('date_default_timezone'), $options); // Wildcard get.
$var_export = $this->getOutput();
eval($var_export);
$this->assertEquals('US/Mountain', $variables['date_default_timezone'], 'Variable was successfully set and get.');
$this->drush('variable-set', array('site_name', 'unish'), $options + array('always-set' => NULL));
$this->drush('variable-get', array('site_name'), $options);
$var_export = $this->getOutput();
eval($var_export);
$this->assertEquals('unish', $variables['site_name'], '--always-set option works as expected.');
$this->drush('variable-delete', array('site_name'), $options);
$output = $this->getOutput();
$this->assertEmpty($output, 'Variable was successfully deleted.');
}
}