First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

10
vendor/drush/drush/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
tests/phpunit.xml
vendor
/lib/Console_Table-*
box.phar
drush.phar
isolation/
#The mkdocs output directory.
site
# IDE config
.idea/

143
vendor/drush/drush/.travis.yml vendored Normal file
View file

@ -0,0 +1,143 @@
# Configuration file for unit test runner at http://travis-ci.org/#!/drush-ops/drush
branches:
only:
- master
- 8.x
- 7.x
- 6.x
- 5.x
- /^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+.*$/
language: php
php:
# See master-fulltest branch for broader PHP version testing.
- 5.4
- 7.0
# Cache Composer & Unish directories.
cache:
directories:
- $HOME/.composer/cache
- /tmp/unish
# http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/
sudo: false
env:
matrix:
#D6
- UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--group=base
- UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--group=commands
- UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--group=pm
- UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
#D7
- UNISH_DRUPAL_MAJOR_VERSION=7 PHPUNIT_ARGS=--group=make
- UNISH_DRUPAL_MAJOR_VERSION=7 PHPUNIT_ARGS=--group=base
- UNISH_DRUPAL_MAJOR_VERSION=7 PHPUNIT_ARGS=--group=commands
- UNISH_DRUPAL_MAJOR_VERSION=7 PHPUNIT_ARGS=--group=pm
- UNISH_DRUPAL_MAJOR_VERSION=7 PHPUNIT_ARGS=--group=quick-drupal
- UNISH_DRUPAL_MAJOR_VERSION=7 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
#D8.3.x
- UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=make
- UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=base
- UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=commands
- UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=pm
- UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=quick-drupal
- UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal TEST_CHILDREN="drush-ops/config-extra"
#D8.4.x
- UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=make
- UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=base
- UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=commands
- UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=pm
- UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=quick-drupal
- UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
# - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=make
# - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=base
# - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=commands
# - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=pm
# - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--group=quick-drupal
# - UNISH_DB_URL=sqlite://none/of/this/matters PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
# - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=make
# - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=base
# - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=commands
# - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=pm
# - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--group=quick-drupal
# - UNISH_DB_URL=pgsql://postgres:@localhost PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
global:
# Github deploy
- secure: VfYokT2CchfuBRJp9/gSwfVGPfsVfkZdDVEuNWEqxww3z4vq+5aLKqoCtPL54E5EIMjhyCE3GVo+biG35Gab1KOVgUs8zD1hAUWA1FPKfMFhoPDfI3ZJC2rX2T1iWK4ZR90pBtcPzS+2OObzTYz8go0PfeSTT6eq69Na1KcNLaE=
- UNISH_NO_TIMEOUTS=y
- UNISH_DB_URL=mysql://root:@127.0.0.1
matrix:
exclude:
# Drupal 6 does not work with php 7, so skip all of the Drupal 6 tests with this php.
- php: 7.0
env: UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--group=base
- php: 7.0
env: UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--group=commands
- php: 7.0
env: UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--group=pm
- php: 7.0
env: UNISH_DRUPAL_MAJOR_VERSION=6 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
# Drupal 8 requires a minimum php of 5.5, so skip all of the Drupal 8 tests with this php.
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=make
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=base
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=commands
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=pm
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--group=quick-drupal
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 UNISH_DRUPAL_MINOR_VERSION=3.7 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal TEST_CHILDREN="drush-ops/config-extra"
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=make
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=base
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=commands
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=pm
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--group=quick-drupal
- php: 5.4
env: UNISH_DRUPAL_MAJOR_VERSION=8 PHPUNIT_ARGS=--exclude-group=base,make,commands,pm,quick-drupal
before_install:
- echo 'mbstring.http_input = pass' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- echo 'mbstring.http_output = pass' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- travis_retry composer selfupdate
install: travis_retry composer install --no-interaction
before_script:
- phpenv config-rm xdebug.ini
- echo 'sendmail_path = /bin/true' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
# - echo "sendmail_path='true'" >> `php --ini | grep "Loaded Configuration" | awk '{print $4}'`
- export UNISH_DRUSH="${PWD}/drush"
script: ${PWD}/unish.sh $PHPUNIT_ARGS
# Background: https://github.com/drush-ops/drush/pull/1426
after_success: ${PWD}/tests/testChildren.sh
before_deploy:
- gem install mime-types -v 2.6.2 # https://github.com/travis-ci/travis-ci/issues/5145
- curl -LSs https://box-project.github.io/box2/installer.php | php
- php box.phar build
- test $TRAVIS_TAG=true && mkdir s3-stable && cp drush.phar s3-stable/drush.phar
- test $TRAVIS_BRANCH=master && mkdir s3-unstable && cp drush.phar s3-unstable/drush-unstable.phar
deploy:
# http://docs.travis-ci.com/user/deployment/releases/
- provider: releases
skip_cleanup: true
api_key:
secure: vRtKwJNzm+FXS5VCsaCu5YM4IS02tAdqV4G557HEyVzNgRmSgPWkaHDR/95TnXtZRjmxuTI++rccEj9+jzjknQ9LWkWhl13WiJdZYobnb240f9Ja8g0gs6/r+EEZG2+DTTerK9zicpf51h5hUaE46zObHjSmzIuGxZBO1syDld8=
file: drush.phar
on:
tags: true
repo: drush-ops/drush
all_branches: true

22
vendor/drush/drush/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,22 @@
Drush is built by people like you! Please [join us](https://github.com/drush-ops/drush).
## Git and Pull requests
* Contributions are submitted, reviewed, and accepted using Github pull requests. [Read this article](https://help.github.com/articles/using-pull-requests) for some details. We use the _Fork and Pull_ model, as described there.
* To help keep track of [your assigned issues](https://github.com/dashboard/issues/assigned), simply open an issue to be added as an [Outside Collaborator](https://github.com/orgs/drush-ops/outside-collaborators). A maintainer can now assign any issue to you at your request.
* The latest changes are in the `master` branch.
* Make a new branch for every feature you're working on.
* Try to make clean commits that are easily readable (including descriptive commit messages!)
* Test before you push. Get familiar with Unish, our test suite. See the test-specific [README.md](tests/README.md)
* Make small pull requests that are easy to review but make sure they do add value by themselves.
* We maintain branches named 7.x, 6.x, etc. These are release branches. From these branches, we make new tags for patch and minor versions.
## Coding style
* Do write comments. You don't have to comment every line, but if you come up with something thats a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are nearly worthless!
* We use [Drupal's coding standards](https://drupal.org/coding-standards).
* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!
* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)
* Keep it compatible. Do not introduce changes to the public API, or configurations too lightly. Don't make incompatible changes without good reasons!
## Documentation
* The docs are in the [docs](docs) and [examples](examples) folders in the git repository, so people can easily find the suitable docs for the current git revision. You can read these from within Drush, with the `drush topic` command.
* Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.

46
vendor/drush/drush/README.md vendored Normal file
View file

@ -0,0 +1,46 @@
Drush is a command line shell and Unix scripting interface for Drupal. Drush core ships with lots of useful commands for interacting with code like modules/themes/profiles. Similarly, it runs update.php, executes sql queries and DB migrations, and misc utilities like run cron or clear cache. Drush can be extended by [3rd party commandfiles](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654).
[![Latest Stable Version](https://poser.pugx.org/drush/drush/v/stable.png)](https://packagist.org/packages/drush/drush) [![Total Downloads](https://poser.pugx.org/drush/drush/downloads.png)](https://packagist.org/packages/drush/drush) [![Latest Unstable Version](https://poser.pugx.org/drush/drush/v/unstable.png)](https://packagist.org/packages/drush/drush) [![License](https://poser.pugx.org/drush/drush/license.png)](https://packagist.org/packages/drush/drush) [![Documentation Status](https://readthedocs.org/projects/drush/badge/?version=master)](https://readthedocs.org/projects/drush/?badge=master)
Resources
-----------
* [Installing (and Upgrading)](http://docs.drush.org/en/master/install/)
* [General Documentation](http://docs.drush.org)
* [API Documentation](http://api.drush.org)
* [Drush Commands](http://drushcommands.com)
* Subscribe [this atom feed](https://github.com/drush-ops/drush/releases.atom) to receive notification on new releases. Also, [Version eye](https://www.versioneye.com/).
* [Drush packages available via Composer](https://packagist.org/search/?type=drupal-drush)
* [A list of modules that include Drush integration](https://www.drupal.org/project/project_module?f[2]=im_vid_3%3A4654&solrsort=ds_project_latest_release+desc)
* Drush comes with a [full test suite](https://github.com/drush-ops/drush/blob/master/tests/README.md) powered by [PHPUnit](https://github.com/sebastianbergmann/phpunit). Each commit gets tested by the awesome [Travis.ci continuous integration service](https://travis-ci.org/drush-ops/drush).
Support
-----------
* Post support requests to [Drupal Answers](http://drupal.stackexchange.com/questions/tagged/drush).
* Report bugs and request features in the [GitHub Drush Issue Queue](https://github.com/drush-ops/drush/issues).
* Use pull requests (PRs) to contribute to Drush.
FAQ
------
> Q: What does "drush" stand for?<br>
> A: The Drupal Shell.
>
> Q: How do I pronounce Drush?<br>
> A: Some people pronounce the *dru* with a long 'u' like Dr*u*pal. Fidelity points
> go to them, but they are in the minority. Most pronounce Drush so that it
> rhymes with hush, rush, flush, etc. This is the preferred pronunciation.
>
> Q: Does Drush have unit tests?<br>
> A: Drush has an excellent suite of unit tests. See
> [tests/README.md](https://github.com/drush-ops/drush/blob/master/tests/README.md) for more information.
Credits
-----------
* Originally developed by [Arto Bendiken](http://bendiken.net) for Drupal 4.7.
* Redesigned by [Franz Heinzmann](http://unbiskant.org) in May 2007 for Drupal 5.
* Maintained by [Moshe Weitzman](http://drupal.org/moshe) with much help from
the folks listed at https://github.com/orgs/drush-ops/people.
![Drush Logo](drush_logo-black.png)

88
vendor/drush/drush/appveyor.yml vendored Normal file
View file

@ -0,0 +1,88 @@
build: false
shallow_clone: true
platform: 'x86'
clone_folder: C:\projects\drush
matrix:
allow_failures:
# Don't record a failure until we have a passing test suite.
- UNISH_NO_TIMEOUTS: y
branches:
only:
- master
- 8.x
init:
#https://github.com/composer/composer/blob/master/appveyor.yml
#- SET ANSICON=121x90 (121x90)
# Inspired by https://github.com/Codeception/base/blob/master/appveyor.yml and https://github.com/phpmd/phpmd/blob/master/appveyor.yml
install:
- cinst -y curl
- SET PATH=C:\Program Files\curl;%PATH%
#which is only needed by the test suite.
- cinst -y which
- SET PATH=C:\Program Files\which;%PATH%
- git clone -q https://github.com/acquia/DevDesktopCommon.git #For tar, cksum, ...
- SET PATH=%APPVEYOR_BUILD_FOLDER%/DevDesktopCommon/bintools-win/msys/bin;%PATH%
- SET PATH=C:\Program Files\MySql\MySQL Server 5.7\bin\;%PATH%
#Install PHP
- cinst -y php
- cd c:\tools\php
- copy php.ini-production php.ini
- echo extension_dir=ext >> php.ini
- echo extension=php_openssl.dll >> php.ini
- echo date.timezone="UTC" >> php.ini
- echo variables_order="EGPCS" >> php.ini #Debugging related to https://github.com/drush-ops/drush/pull/646. May be unneeded.
- echo mbstring.http_input=pass >> php.ini
- echo mbstring.http_output=pass >> php.ini
- echo sendmail_path=nul >> php.ini
- echo extension=php_mbstring.dll >> php.ini
- echo extension=php_curl.dll >> php.ini
- echo extension=php_pdo_mysql.dll >> php.ini
- echo extension=php_pdo_pgsql.dll >> php.ini
- echo extension=php_pdo_sqlite.dll >> php.ini
- echo extension=php_pgsql.dll >> php.ini
- echo extension=php_gd2.dll >> php.ini
- SET PATH=C:\tools\php;%PATH%
#Install Composer
- cd %APPVEYOR_BUILD_FOLDER%
#- appveyor DownloadFile https://getcomposer.org/composer.phar
- php -r "readfile('http://getcomposer.org/installer');" | php
#Install Drush's dependencies via Composer
- php composer.phar -q install --prefer-dist -n
- SET PATH=%APPVEYOR_BUILD_FOLDER%;%APPVEYOR_BUILD_FOLDER%/vendor/bin;%PATH%
#Create a sandbox for testing.
- mkdir c:\drush_temp
services:
- mysql
test_script:
- phpunit --configuration tests %PHPUNIT_ARGS%
# environment variables
environment:
global:
UNISH_NO_TIMEOUTS: y
UNISH_DB_URL: "mysql://root:Password12!@localhost"
UNISH_TMP: c:\drush_temp
matrix:
- UNISH_DRUPAL_MAJOR_VERSION: 7
PHPUNIT_ARGS: --group=base
- UNISH_DRUPAL_MAJOR_VERSION: 7
PHPUNIT_ARGS: --group=commands
- UNISH_DRUPAL_MAJOR_VERSION: 7
PHPUNIT_ARGS: --group=pm
- UNISH_DRUPAL_MAJOR_VERSION: 7
PHPUNIT_ARGS: --group=quick-drupal
- UNISH_DRUPAL_MAJOR_VERSION: 7
PHPUNIT_ARGS: --exclude-group=base,make,commands,pm,quick-drupal
- UNISH_DRUPAL_MAJOR_VERSION: 7
PHPUNIT_ARGS: --group=make

27
vendor/drush/drush/box.json.dist vendored Normal file
View file

@ -0,0 +1,27 @@
{
"alias": "drush.phar",
"chmod": "0755",
"compactors": [],
"directories": ["commands", "docs", "examples", "includes", "lib", "misc"],
"files": ["drush.api.php", "drush.complete.sh", "drush.info", "drush.launcher", "drush.php", "drush_logo-black.png", "README.md"],
"finder": [
{
"name": "*.php",
"exclude": [
"isolation",
"phing",
"test",
"tests",
"Test",
"Tests",
"Tester"
],
"in": "vendor"
}
],
"git-commit": "git-commit",
"git-version": "git-version",
"output": "drush.phar",
"main": "drush",
"stub": true
}

15
vendor/drush/drush/circle.yml vendored Normal file
View file

@ -0,0 +1,15 @@
machine:
timezone:
America/Chicago
php:
version: 7.0.11
dependencies:
cache_directories:
- ~/.composer/cache
override:
- composer install --prefer-dist --no-interaction
test:
override:
- composer lint

View file

@ -0,0 +1,470 @@
<?php
/**
* @file
* An early implementation of Site Archive dump/restore. See
* http://groups.drupal.org/site-archive-format.
*/
use Drush\Log\LogLevel;
function archive_drush_command() {
$items['archive-dump'] = array(
'description' => 'Backup your code, files, and database into a single file.',
'arguments' => array(
'sites' => 'Optional. Site specifications, delimited by commas. Typically, list subdirectory name(s) under /sites.',
),
// Most options on sql-dump should not be shown, so just offer a subset.
'options' => drush_sql_get_table_selection_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. Default is --no-overwrite.',
'generator' => 'The generator name to store in the MANIFEST file. The default is "Drush archive-dump".',
'generatorversion' => 'The generator version number to store in the MANIFEST file. The default is ' . DRUSH_VERSION . '.',
'pipe' => 'Only print the destination of the archive. Useful for scripts that don\'t pass --destination.',
'preserve-symlinks' => 'Preserve symbolic links.',
'no-core' => 'Exclude Drupal core, so the backup only contains the site specific stuff.',
'tar-options' => 'Options passed thru to the tar command.',
),
'examples' => array(
'drush archive-dump default,example.com,foo.com' => 'Write an archive containing 3 sites in it.',
'drush archive-dump @sites' => 'Save archive containing all sites in a multi-site.',
'drush archive-dump default --destination=/backups/mysite.tar' => 'Save archive to custom location.',
'drush archive-dump --tar-options="--exclude=.git --exclude=sites/default/files"' => 'Omits any .git directories found in the tree as well as sites/default/files.',
'drush archive-dump --tar-options="--exclude=%files"' => 'Placeholder %files is replaced with the real path for the current site, and that path is excluded.',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
'aliases' => array('ard', 'archive-backup', 'arb'),
);
$items['archive-restore'] = array(
'description' => 'Expand a site archive into a Drupal web site.',
'arguments' => array(
'file' => 'The site archive file that should be expanded.',
'site name' => 'Optional. Which site within the archive you want to restore. Defaults to all.',
),
'required-arguments' => 1,
'options' => array(
'destination' => 'Specify where the Drupal site should be expanded, including the docroot. Defaults to the current working directory.',
'db-prefix' => 'An optional table prefix to use during restore.',
'db-url' => array(
'description' => 'A Drupal 6 style database URL indicating the target for database restore. If not provided, the archived settings.php is used.',
'example-value' => 'mysql://root:pass@host/db',
),
'db-su' => 'Account to use when creating the new database. Optional.',
'db-su-pw' => 'Password for the "db-su" account. Optional.',
'overwrite' => 'Allow drush to overwrite any files in the destination. Default is --no-overwrite.',
'tar-options' => 'Options passed thru to the tar command.',
),
'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_NONE,
'aliases' => array('arr'),
);
return $items;
}
/**
* Command callback. Generate site archive file.
*/
function drush_archive_dump($sites_subdirs = '@self') {
$include_platform = !drush_get_option('no-core', FALSE);
$tar = drush_get_tar_executable();
$sites = array();
list($aliases, $not_found) = drush_sitealias_resolve_sitespecs(explode(',', $sites_subdirs));
if (!empty($not_found)) {
drush_log(dt("Some site subdirectories are not valid Drupal sites: @list", array("@list" => implode(', ', $not_found))), LogLevel::WARNING);
}
foreach ($aliases as $key => $alias) {
$sites[$key] = $alias;
if (($db_record = sitealias_get_databases_from_record($alias))) {
$sites[$key]['databases'] = $db_record;
}
else {
$sites[$key]['databases'] = array();
drush_log(dt('DB definition not found for !alias', array('!alias' => $key)), LogLevel::NOTICE);
}
}
// The user can specify a destination filepath or not. That filepath might
// end with .gz, .tgz, or something else. At the end of this command we will
// gzip a file, and we want it to end up with the user-specified name (if
// any), but gzip renames files and refuses to compress files ending with
// .gz and .tgz, making our lives difficult. Solution:
//
// 1. Create a unique temporary base name to which gzip WILL append .gz.
// 2. If no destination is provided, set $dest_dir to a backup directory and
// $final_destination to be the unique name in that dir.
// 3. If a destination is provided, set $dest_dir to that directory and
// $final_destination to the exact name given.
// 4. Set $destination, the actual working file we will build up, to the
// unqiue name in $dest_dir.
// 5. After gzip'ing $destination, rename $destination.gz to
// $final_destination.
//
// Sheesh.
// Create the unique temporary name.
$prefix = 'none';
if (!empty($sites)) {
$first = current($sites);
if ( !empty($first['databases']['default']['default']['database']) ) {
$prefix = count($sites) > 1 ? 'multiple_sites' : str_replace('/', '-', $first['databases']['default']['default']['database']);
}
}
$date = gmdate('Ymd_His');
$temp_dest_name = "$prefix.$date.tar";
$final_destination = drush_get_option('destination');
if (!$final_destination) {
// No destination provided.
$backup = drush_include_engine('version_control', 'backup');
// TODO: this standard Drush pattern leads to a slightly obtuse directory structure.
$dest_dir = $backup->prepare_backup_dir('archive-dump');
if (empty($dest_dir)) {
$dest_dir = drush_tempdir();
}
$final_destination = "$dest_dir/$temp_dest_name.gz";
}
else {
// Use the supplied --destination. If it is relative, resolve it
// relative to the directory in which drush was invoked.
$command_cwd = getcwd();
drush_op('chdir', drush_get_context('DRUSH_OLDCWD', getcwd()));
// This doesn't perform realpath on the basename, but that's okay. This is
// not path-based security. We just use it for checking for perms later.
drush_mkdir(dirname($final_destination));
$dest_dir = realpath(dirname($final_destination));
$final_destination = $dest_dir . '/' . basename($final_destination);
drush_op('chdir', $command_cwd);
}
// $dest_dir is either the backup directory or specified directory. Set our
// working file.
$destination = "$dest_dir/$temp_dest_name";
// Validate the FINAL destination. It should be a file that does not exist
// (unless --overwrite) in a writable directory (and a writable file if
// it exists). We check all this up front to avoid failing after a long
// dump process.
$overwrite = drush_get_option('overwrite');
$dest_dir = dirname($final_destination);
$dt_args = array('!file' => $final_destination, '!dir' => $dest_dir);
if (is_dir($final_destination)) {
drush_set_error('DRUSH_ARCHIVE_DEST_IS_DIR', dt('Destination !file must be a file, not a directory.', $dt_args));
return;
}
else if (file_exists($final_destination)) {
if (!$overwrite) {
drush_set_error('DRUSH_ARCHIVE_DEST_EXISTS', dt('Destination !file exists; specify --overwrite to overwrite.', $dt_args));
return;
}
else if (!is_writable($final_destination)) {
drush_set_error('DRUSH_ARCHIVE_DEST_FILE_NOT_WRITEABLE', dt('Destination !file is not writable.', $dt_args));
return;
}
}
else if (!is_writable($dest_dir)) {
drush_set_error('DRUSH_ARCHIVE_DEST_DIR_NOT_WRITEABLE', dt('Destination directory !dir is not writable.', $dt_args));
return;
}
// Get the extra options for tar, if any
$tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', ''));
// Start adding codebase to the archive.
$docroot_path = realpath(drush_get_context('DRUSH_DRUPAL_ROOT'));
$docroot = basename($docroot_path);
$workdir = dirname($docroot_path);
if ($include_platform) {
$dereference = (drush_get_option('preserve-symlinks', FALSE)) ? '' : '--dereference ';
// Convert destination path to Unix style for tar on MinGW - see http://drupal.org/node/1844224
if (drush_is_mingw()) {
$destination_orig = $destination;
$destination = str_replace('\\', '/', $destination);
$destination = preg_replace('$^([a-zA-Z]):$', '/$1', $destination);
}
// Archive Drupal core, excluding sites dir.
drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --exclude \"{$docroot}/sites\" {$dereference}-cf %s %s", $destination, $docroot);
// Add sites/all to the same archive.
drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} {$dereference}-rf %s %s", $destination, "{$docroot}/sites/all");
// Add special files in sites/ to the archive. Last 2 items are new in Drupal8.
$files_to_add = array('sites/README.txt', 'sites/sites.php', 'sites/example.sites.php', 'sites/development.services.yml', 'sites/example.settings.local.php');
foreach ($files_to_add as $file_to_add) {
if (file_exists($file_to_add)) {
drush_shell_cd_and_exec($workdir, "$tar {$dereference}-rf %s %s", $destination, $docroot . '/' . $file_to_add);
}
}
}
$tmp = drush_tempdir();
$all_dbs = array();
// Dump the default database for each site and add to the archive.
foreach ($sites as $key => $alias) {
if (isset($alias['databases']['default']['default'])) {
$db_spec = $alias['databases']['default']['default'];
// Use a subdirectory name matching the docroot name.
drush_mkdir("{$tmp}/{$docroot}");
// Ensure uniqueness by prefixing key if needed. Remove path delimiters.
$dbname = str_replace(DIRECTORY_SEPARATOR, '-', $db_spec['database']);
$result_file = count($sites) == 1 ? "$tmp/$dbname.sql" : str_replace('@', '', "$tmp/$key-$dbname.sql");
$all_dbs[$key] = array(
'file' => basename($result_file),
'driver' => $db_spec['driver'],
);
$sql = drush_sql_get_class($db_spec);
$sql->dump($result_file);
drush_shell_cd_and_exec($tmp, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, basename($result_file));
}
}
// Build a manifest file AND add sites/$subdir to archive as we go.
$platform = array(
'datestamp' => time(),
'formatversion' => '1.0',
'generator' => drush_get_option('generator', 'Drush archive-dump'),
'generatorversion' => drush_get_option('generatorversion', DRUSH_VERSION),
'description' => drush_get_option('description', ''),
'tags' => drush_get_option('tags', ''),
'archiveformat' => ($include_platform ? 'platform' : 'site'),
);
$contents = drush_export_ini(array('Global' => $platform));
$i=0;
foreach ($sites as $key => $alias) {
$status = drush_invoke_process($alias, 'core-status', array(), array(), array('integrate' => FALSE));
if (!$status || $status['error_log']) {
drush_log(dt('Unable to determine sites directory for !alias', array('!alias' => $key)), LogLevel::WARNING);
}
// Add the site specific directory to archive.
if (!empty($status['object']['%paths']['%site'])) {
drush_shell_cd_and_exec($workdir, "$tar {$tar_extra_options} --dereference -rf %s %s", $destination, "{$docroot}/sites/" . basename($status['object']['%paths']['%site']));
}
$site = array(
'docroot' => DRUPAL_ROOT,
'sitedir' => @$status['object']['%paths']['%site'],
'files-public' => @$status['object']['%paths']['%files'],
'files-private' => @$status['object']['%paths']['%private'],
);
$site["database-default-file"] = $all_dbs[$key]['file'];
$site["database-default-driver"] = $all_dbs[$key]['driver'];
// The section title is the sites subdirectory name.
$info[basename($site['sitedir'])] = $site;
$contents .= "\n" . drush_export_ini($info);
unset($info);
$i++;
}
file_put_contents("{$tmp}/MANIFEST.ini", $contents);
// Add manifest to archive.
drush_shell_cd_and_exec($tmp, "$tar --dereference -rf %s %s", $destination, 'MANIFEST.ini');
// Ensure that default/default.settings.php is in the archive. This is needed
// by site-install when restoring a site without any DB.
// NOTE: Windows tar file replace operation is broken so we have to check if file already exists.
// Otherwise it will corrupt the archive.
$res = drush_shell_cd_and_exec($workdir, "$tar -tf %s %s", $destination, $docroot . '/sites/default/default.settings.php');
$output = drush_shell_exec_output();
if (!$res || !isset($output[0]) || empty($output[0])) {
drush_shell_cd_and_exec($workdir, "$tar --dereference -vrf %s %s", $destination, $docroot . '/sites/default/default.settings.php');
}
// Switch back to original destination in case it was modified for tar on MinGW.
if (!empty($destination_orig)) {
$destination = $destination_orig;
}
// Compress the archive
if (!drush_shell_exec("gzip --no-name -f %s", $destination)) {
// Clean up the tar file
drush_register_file_for_deletion($destination);
return drush_set_error(DRUSH_APPLICATION_ERROR, dt('Failed to gzip !destination', ['!destination' => $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)), LogLevel::OK);
return $final_destination;
}
/**
* Command argument complete callback.
*
* @return
* List of site names/aliases for archival.
*/
function archive_archive_dump_complete() {
return array('values' => array_keys(_drush_sitealias_all_list()));
}
/**
* Command callback. Restore web site(s) from a site archive file.
*/
function drush_archive_restore($file, $site_id = NULL) {
$tmp = drush_tempdir();
// Get the extra options for tar, if any
$tar_extra_options = drush_sitealias_evaluate_paths_in_options(drush_get_option('tar-options', ''));
if (!$files = drush_tarball_extract($file, $tmp, FALSE, $tar_extra_options)) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_EXTRACT', dt('Unable to extract site archive tarball to !tmp.', array('!tmp' => $tmp)));
}
$manifest = $tmp . '/MANIFEST.ini';
if (file_exists($manifest)) {
if (!$ini = parse_ini_file($manifest, TRUE)) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_PARSE_MANIFEST', dt('Unable to parse MANIFEST.ini in the archive.'));
}
}
else {
$ini = drush_archive_guess_manifest($tmp);
}
// Backward compatibility: 'archiveformat' did not exist
// in older versions of archive-dump.
if (!isset( $ini['Global']['archiveformat'])) {
$ini['Global']['archiveformat'] = 'platform';
}
// Grab the first site in the Manifest and move docroot to destination.
$ini_tmp = $ini;
unset($ini_tmp['Global']);
$first = array_shift($ini_tmp);
$docroot = basename($first['docroot']);
$destination = drush_get_option('destination', realpath('.') . "/$docroot");
if ($ini['Global']['archiveformat'] == 'platform') {
// Move the whole platform in-place at once.
if (!drush_move_dir("$tmp/$docroot", $destination, drush_get_option('overwrite'))) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore platform to !dest', array('!dest' => $destination)));
}
}
else {
// When no platform is included we do this on a per-site basis.
}
// Loop over sites and restore databases and append to settings.php.
foreach ($ini as $section => $site) {
if ($section != 'Global' && (!isset($site_id) || $section == $site_id) && !empty($site['database-default-file'])) {
$site_destination = $destination . '/' . $site['sitedir'];
// Restore site, in case not already done above.
if ($ini['Global']['archiveformat'] == 'site') {
if (!drush_move_dir("$tmp/$docroot/" . $site['sitedir'], $site_destination, drush_get_option('overwrite'))) {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_TO_RESTORE_FILES', dt('Unable to restore site to !dest', array('!dest' => $site_destination)));
}
}
// Restore database.
$sql_file = $tmp . '/' . $site['database-default-file'];
if ($db_url = drush_get_option('db-url')) {
if (empty($site_id) && count($ini) >= 3) {
// TODO: Use drushrc to provide multiple db-urls for multi-restore?
return drush_set_error('DRUSH_ARCHIVE_UNABLE_MULTI_RESTORE', dt('You must specify a site to restore when the archive contains multiple sites and a db-url is provided.'));
}
$db_spec = drush_convert_db_from_db_url($db_url);
}
else {
$site_specification = $destination . '#' . $section;
if ($return = drush_invoke_process($site_specification, 'sql-conf', array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE))) {
$databases = $return['object'];
$db_spec = $databases['default']['default'];
}
else {
return drush_set_error('DRUSH_ARCHIVE_UNABLE_DISCOVER_DB', dt('Unable to get database details from db-url option or settings.php', array('!key' => $key)));
}
}
$sql = drush_sql_get_class($db_spec);
$sql->drop_or_create();
$sql->query(NULL, $sql_file);
// Append new DB info to settings.php.
if ($db_url) {
$settingsfile = $destination . '/' . $site['sitedir'] . '/settings.php';
//If settings.php doesn't exist in the archive, create it from default.settings.php.
if (!file_exists($settingsfile)) {
drush_op('copy', $destination . '/sites/default/default.settings.php', $settingsfile);
}
// Need to do something here or else we can't write.
chmod($settingsfile, 0664);
file_put_contents($settingsfile, "\n// Appended by drush archive-restore command.\n", FILE_APPEND);
if (drush_drupal_major_version($destination) >= 7) {
file_put_contents($settingsfile, "\n" . '$databases = ' . var_export(drush_sitealias_convert_db_from_db_url($db_url), TRUE) . ";\n", FILE_APPEND);
}
else {
file_put_contents($settingsfile, "\n" . '$db_url = \'' . $db_url . "';\n", FILE_APPEND);
}
drush_log(dt('Drush appended the new database configuration at settings.php. Optionally remove the old configuration manually.'), LogLevel::OK);
}
}
}
drush_log(dt('Archive restored to !dest', array('!dest' => $destination)), LogLevel::OK);
return $destination;
}
/**
* Command argument complete callback.
*
* @return
* Strong glob of files to complete on.
*/
function archive_archive_restore_complete() {
return array(
'files' => array(
'directories' => array(
'pattern' => '*',
'flags' => GLOB_ONLYDIR,
),
'tar' => array(
'pattern' => '*.tar.gz',
),
),
);
}
/**
* Try to find docroot and DB dump file in an extracted archive.
*
* @param string $path The location of the extracted archive.
* @return array The manifest data.
*/
function drush_archive_guess_manifest($path) {
$db_file = drush_scan_directory($path, '/\.sql$/', array('.', '..', 'CVS'), 0, 0);
if (file_exists($path . '/index.php')) {
$docroot = './';
}
else {
$directories = glob($path . '/*' , GLOB_ONLYDIR);
$docroot = reset($directories);
}
$ini = array(
'Global' => array(
// Very crude detection of a platform...
'archiveformat' => (drush_drupal_version($docroot) ? 'platform' : 'site'),
),
'default' => array(
'docroot' => $docroot,
'sitedir' => 'sites/default',
'database-default-file' => key($db_file),
),
);
return $ini;
}

View file

@ -0,0 +1,309 @@
<?php
use Drush\Log\LogLevel;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Request;
/**
* Implementation of hook_drush_help().
*/
function cache_drush_help($section) {
switch ($section) {
case 'meta:cache:title':
return dt('Cache commands');
case 'meta:cache:summary':
return dt('Interact with Drupal\'s cache API.');
}
}
/**
* Implementation of hook_drush_command().
*/
function cache_drush_command() {
$items = array();
// We specify command callbacks here because the defaults would collide with
// the drush cache api functions.
$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.',
),
'required-arguments' => 1,
'callback' => 'drush_cache_command_get',
'outputformat' => array(
'default' => 'print-r',
'pipe-format' => 'var_export',
'output-data-type' => TRUE,
),
'aliases' => array('cg'),
);
$items['cache-clear'] = array(
'bootstrap' => DRUSH_BOOTSTRAP_MAX,
'description' => 'Clear a specific cache, or all drupal caches.',
'arguments' => array(
'type' => 'The particular cache to clear. Omit this argument to choose from available caches.',
),
'callback' => 'drush_cache_command_clear',
'aliases' => array('cc'),
);
$items['cache-set'] = array(
'description' => 'Cache an object expressed in JSON or var_export() format.',
'arguments' => array(
'cid' => 'The id of the object to set.',
'data' => 'The object to set in the cache. Use \'-\' to read the object from STDIN.',
'bin' => 'Optional. The cache bin to store the object in.',
'expire' => 'Optional. CACHE_PERMANENT, CACHE_TEMPORARY, or a Unix timestamp.',
'tags' => 'An array of cache tags.',
),
'required-arguments' => 2,
'options' => array(
// Note that this is not an outputformat option.
'format' => 'Format to parse the object. Use "string" for string (default), and "json" for JSON.',
'cache-get' => 'If the object is the result a previous fetch from the cache, only store the value in the "data" property of the object in the cache.',
),
'callback' => 'drush_cache_command_set',
'aliases' => array('cs'),
);
$items['cache-rebuild'] = array(
'description' => 'Rebuild a Drupal 8 site and clear all its caches.',
'options' => array(),
'arguments' => array(),
// Bootstrap to DRUSH_BOOTSTAP_DRUPAL_SITE to pick the correct site.
// Further bootstrap is done by the rebuild script.
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
'core' => array('8+'),
'aliases' => array('cr', 'rebuild'),
);
return $items;
}
/**
* Command argument complete callback.
*
* @return
* Array of clear types.
*/
function cache_cache_clear_complete() {
// Bootstrap as far as possible so that Views and others can list their caches.
drush_bootstrap_max();
return array('values' => array_keys(drush_cache_clear_types(TRUE)));
}
function drush_cache_clear_pre_validate($type = NULL) {
$types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL));
// Check if the provided type ($type) is a valid cache type.
if ($type && !array_key_exists($type, $types)) {
if ($type === 'all' && drush_drupal_major_version() >= 8) {
return drush_set_error(dt('`cache-clear all` is deprecated for Drupal 8 and later. Please use the `cache-rebuild` command instead.'));
}
// If we haven't done a full bootstrap, provide a more
// specific message with instructions to the user on
// bootstrapping a Drupal site for more options.
if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
$all_types = drush_cache_clear_types(TRUE);
if (array_key_exists($type, $all_types)) {
return drush_set_error(dt("'!type' cache requires a working Drupal site to operate on. Use the --root and --uri options, or a site @alias, or cd to a directory containing a Drupal settings.php file.", array('!type' => $type)));
}
else {
return drush_set_error(dt("'!type' cache is not a valid cache type. There may be more cache types available if you select a working Drupal site.", array('!type' => $type)));
}
}
return drush_set_error(dt("'!type' cache is not a valid cache type.", array('!type' => $type)));
}
}
/**
* Command callback for drush cache-clear.
*/
function drush_cache_command_clear($type = NULL) {
if (!drush_get_option('cache-clear', TRUE)) {
drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::OK);
return TRUE;
}
$types = drush_cache_clear_types(drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL));
if (!isset($type)) {
// Don't offer 'all' unless Drush has bootstrapped the Drupal site
if (!drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
unset($types['all']);
}
$type = drush_choice($types, 'Enter a number to choose which cache to clear.', '!key');
if (empty($type)) {
return drush_user_abort();
}
}
// Do it.
drush_op($types[$type]);
if ($type == 'all' && !drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
drush_log(dt("No Drupal site found, only 'drush' cache was cleared."), LogLevel::WARNING);
}
else {
drush_log(dt("'!name' cache was cleared.", array('!name' => $type)), LogLevel::SUCCESS);
}
}
/**
* Print an object returned from the cache.
*
* @param $cid
* The cache ID of the object to fetch.
* @param $bin
* A specific bin to fetch from. If not specified, the default bin is used.
*/
function drush_cache_command_get($cid = NULL, $bin = NULL) {
drush_include_engine('drupal', 'cache');
$result = drush_op('_drush_cache_command_get', $cid, $bin);
if (empty($result)) {
return drush_set_error('DRUSH_CACHE_OBJECT_NOT_FOUND', dt('The !cid object in the !bin bin was not found.', array('!cid' => $cid, '!bin' => $bin ? $bin : _drush_cache_bin_default())));
}
return $result;
}
/**
* 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
* A specific bin to fetch from. If not specified, the default bin is used.
* @param $expire
* The expiry timestamp for the cached object.
* @param $tags
* Cache tags for the cached object.
*/
function drush_cache_command_set($cid = NULL, $data = '', $bin = NULL, $expire = NULL, $tags = array()) {
// In addition to prepare, this also validates. Can't easily be in own validate callback as
// reading once from STDIN empties it.
$data = drush_cache_set_prepare_data($data);
if ($data === FALSE && drush_get_error()) {
// An error was logged above.
return;
}
drush_include_engine('drupal', 'cache');
return drush_op('_drush_cache_command_set', $cid, $data, $bin, $expire, $tags);
}
function drush_cache_set_prepare_data($data) {
if ($data == '-') {
$data = file_get_contents("php://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 might be an object.
if (is_object($data) && $data->data) {
$data = $data->data;
}
// But $data returned from `drush cache-get --format=json` will be an array.
elseif (is_array($data) && isset($data['data'])) {
$data = $data['data'];
}
else {
// If $data is neither object nor array and cache-get was specified, then
// there is a problem.
return drush_set_error('CACHE_INVALID_FORMAT', dt("'cache-get' was specified as an option, but the data is neither an object or an array."));
}
}
return $data;
}
/**
* All types of caches available for clearing. Contrib commands can alter in their own.
*/
function drush_cache_clear_types($include_bootstrapped_types = FALSE) {
drush_include_engine('drupal', 'cache');
$types = _drush_cache_clear_types($include_bootstrapped_types);
// 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, $include_bootstrapped_types);
return $types;
}
/**
* Clear caches internal to drush core.
*/
function drush_cache_clear_drush() {
drush_cache_clear_all(NULL, 'default'); // commandfiles, etc.
drush_cache_clear_all(NULL, 'complete'); // completion
// Release XML. We don't clear tarballs since those never change.
$matches = drush_scan_directory(drush_directory_cache('download'), "/^https---updates.drupal.org-release-history/", array('.', '..'));
array_map('unlink', array_keys($matches));
}
/**
* Rebuild a Drupal 8 site.
*
* This is a transpose of core/rebuild.php. Additionally
* it also clears drush cache and drupal render cache.
*/
function drush_cache_rebuild() {
if (!drush_get_option('cache-clear', TRUE)) {
drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::OK);
return TRUE;
}
chdir(DRUPAL_ROOT);
// Clear the APC cache to ensure APC class loader is reset.
if (function_exists('apc_fetch')) {
apc_clear_cache('user');
}
// Clear user cache for all major platforms.
$user_caches = [
'apcu_clear_cache',
'wincache_ucache_clear',
'xcache_clear_cache',
];
foreach (array_filter($user_caches, 'is_callable') as $cache) {
call_user_func($cache);
}
$autoloader = drush_drupal_load_autoloader(DRUPAL_ROOT);
require_once DRUSH_DRUPAL_CORE . '/includes/utility.inc';
$request = Request::createFromGlobals();
// Ensure that the HTTP method is set, which does not happen with Request::createFromGlobals().
$request->setMethod('GET');
// Manually resemble early bootstrap of DrupalKernel::boot().
require_once DRUSH_DRUPAL_CORE . '/includes/bootstrap.inc';
DrupalKernel::bootEnvironment();
// Avoid 'Only variables should be passed by reference'
$root = DRUPAL_ROOT;
$site_path = DrupalKernel::findSitePath($request);
Settings::initialize($root, $site_path, $autoloader);
// Use our error handler since _drupal_log_error() depends on an unavailable theme system (ugh).
set_error_handler('drush_error_handler');
// drupal_rebuild() calls drupal_flush_all_caches() itself, so we don't do it manually.
drupal_rebuild($autoloader, $request);
drush_log(dt('Cache rebuild complete.'), LogLevel::OK);
// As this command replaces `drush cache-clear all` for Drupal 8 users, clear
// the Drush cache as well, for consistency with that behavior.
drush_cache_clear_drush();
}

View file

@ -0,0 +1,263 @@
<?php
use Drush\Log\LogLevel;
use Drupal\Component\Assertion\Handle;
use Drush\Psysh\DrushHelpCommand;
use Drush\Psysh\DrushCommand;
use Drush\Psysh\Shell;
use Psy\VersionUpdater\Checker;
/**
* Implements hook_drush_command().
*/
function cli_drush_command() {
$items['core-cli'] = array(
'description' => 'Open an interactive shell on a Drupal site.',
'remote-tty' => TRUE,
'aliases' => array('php'),
'bootstrap' => DRUSH_BOOTSTRAP_MAX,
'topics' => array('docs-repl'),
'options' => array(
'version-history' => 'Use command history based on Drupal version (Default is per site).',
),
);
$items['docs-repl'] = array(
'description' => 'repl.md',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array(drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH) . '/docs/repl.md'),
);
return $items;
}
/**
* Command callback.
*/
function drush_cli_core_cli() {
$drupal_major_version = drush_drupal_major_version();
$configuration = new \Psy\Configuration();
// Set the Drush specific history file path.
$configuration->setHistoryFile(drush_history_path_cli());
// Disable checking for updates. Our dependencies are managed with Composer.
$configuration->setUpdateCheck(Checker::NEVER);
$shell = new Shell($configuration);
if ($drupal_major_version >= 8) {
// Register the assertion handler so exceptions are thrown instead of errors
// being triggered. This plays nicer with PsySH.
Handle::register();
$shell->setScopeVariables(['container' => \Drupal::getContainer()]);
// Add Drupal 8 specific casters to the shell configuration.
$configuration->addCasters(_drush_core_cli_get_casters());
}
// Add Drush commands to the shell.
$commands = [new DrushHelpCommand()];
foreach (drush_commands_categorize(_drush_core_cli_get_commands()) as $category_data) {
$category_title = (string) $category_data['title'];
foreach ($category_data['commands'] as $command_config) {
$command = new DrushCommand($command_config);
// Set the category label on each.
$command->setCategory($category_title);
$commands[] = $command;
}
}
$shell->addCommands($commands);
// PsySH will never return control to us, but our shutdown handler will still
// run after the user presses ^D. Mark this command as completed to avoid a
// spurious error message.
drush_set_context('DRUSH_EXECUTION_COMPLETED', TRUE);
// Run the terminate event before the shell is run. Otherwise, if the shell
// is forking processes (the default), any child processes will close the
// database connection when they are killed. So when we return back to the
// parent process after, there is no connection. This will be called after the
// command in preflight still, but the subscriber instances are already
// created from before. Call terminate() regardless, this is a no-op for all
// DrupalBoot classes except DrupalBoot8.
if ($bootstrap = drush_get_bootstrap_object()) {
$bootstrap->terminate();
}
// To fix the above problem in Drupal 7, the connection can be closed manually.
// This will make sure a new connection is created again in child loops. So
// any shutdown functions will still run ok after the shell has exited.
if ($drupal_major_version == 7) {
Database::closeConnection();
}
$shell->run();
}
/**
* Returns a filtered list of Drush commands used for CLI commands.
*
* @return array
*/
function _drush_core_cli_get_commands() {
$commands = drush_get_commands();
$ignored_commands = ['help', 'drush-psysh', 'php-eval', 'core-cli', 'php'];
$php_keywords = _drush_core_cli_get_php_keywords();
foreach ($commands as $name => $config) {
// Ignore some commands that don't make sense inside PsySH, are PHP keywords
// are hidden, or are aliases.
if (in_array($name, $ignored_commands) || in_array($name, $php_keywords) || !empty($config['hidden']) || ($name !== $config['command'])) {
unset($commands[$name]);
}
else {
// Make sure the command aliases don't contain any PHP keywords.
if (!empty($config['aliases'])) {
$commands[$name]['aliases'] = array_diff($commands[$name]['aliases'], $php_keywords);
}
}
}
return $commands;
}
/**
* Returns a mapped array of casters for use in the shell.
*
* These are Symfony VarDumper casters.
* See http://symfony.com/doc/current/components/var_dumper/advanced.html#casters
* for more information.
*
* @return array.
* An array of caster callbacks keyed by class or interface.
*/
function _drush_core_cli_get_casters() {
return [
'Drupal\Core\Entity\ContentEntityInterface' => 'Drush\Psysh\Caster::castContentEntity',
'Drupal\Core\Field\FieldItemListInterface' => 'Drush\Psysh\Caster::castFieldItemList',
'Drupal\Core\Field\FieldItemInterface' => 'Drush\Psysh\Caster::castFieldItem',
'Drupal\Core\Config\Entity\ConfigEntityInterface' => 'Drush\Psysh\Caster::castConfigEntity',
'Drupal\Core\Config\ConfigBase' => 'Drush\Psysh\Caster::castConfig',
'Drupal\Component\DependencyInjection\Container' => 'Drush\Psysh\Caster::castContainer',
];
}
/**
* Returns the file path for the CLI history.
*
* This can either be site specific (default) or Drupal version specific.
*
* @return string.
*/
function drush_history_path_cli() {
$cli_directory = drush_directory_cache('cli');
// If only the Drupal version is being used for the history.
if (drush_get_option('version-history', FALSE)) {
$drupal_major_version = drush_drupal_major_version();
$file_name = "drupal-$drupal_major_version";
}
// If there is an alias, use that in the site specific name. Otherwise,
// use a hash of the root path.
else {
if ($alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
$site = drush_sitealias_get_record($alias);
$site_suffix = $site['#name'];
}
else {
$site_suffix = md5(DRUPAL_ROOT);
}
$file_name = "drupal-site-$site_suffix";
}
$full_path = "$cli_directory/$file_name";
// Output the history path if verbose is enabled.
if (drush_get_context('DRUSH_VERBOSE')) {
drush_log(dt('History: @full_path', ['@full_path' => $full_path]), LogLevel::INFO);
}
return $full_path;
}
/**
* Returns a list of PHP keywords.
*
* This will act as a blacklist for command and alias names.
*
* @return array
*/
function _drush_core_cli_get_php_keywords() {
return [
'__halt_compiler',
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'eval',
'exit',
'extends',
'final',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'list',
'namespace',
'new',
'or',
'print',
'private',
'protected',
'public',
'require',
'require_once',
'return',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
];
}

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,310 @@
<?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.md',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/README.md'),
);
$items['docs-bisect'] = array(
'description' => 'git bisect and Drush may be used together to find the commit an error was introduced in.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/git-bisect.example.sh'),
);
$items['docs-bashrc'] = array(
'description' => 'Bashrc customization examples for Drush.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.bashrc'),
);
$items['docs-configuration'] = array(
'description' => 'Configuration overview with examples from example.drushrc.php.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.drushrc.php'),
);
$items['docs-config-exporting'] = array(
'description' => 'Drupal configuration export instructions, including customizing configuration by environment.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/config-exporting.md'),
);
$items['docs-aliases'] = array(
'description' => 'Site aliases overview on creating your own aliases for commonly used Drupal sites with examples from example.aliases.drushrc.php.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.aliases.drushrc.php'),
);
$items['docs-ini-files'] = array(
'description' => 'php.ini or drush.ini configuration to set PHP values for use with Drush.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/example.drush.ini'),
);
$items['docs-bastion'] = array(
'description' => 'Bastion server configuration: remotely operate on a Drupal sites behind a firewall.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/bastion.md'),
);
$items['docs-bootstrap'] = array(
'description' => 'Bootstrap explanation: how Drush starts up and prepares the Drupal environment for use with the command.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/bootstrap.md'),
);
$items['docs-cron'] = array(
'description' => 'Crontab instructions for running your Drupal cron tasks via `drush cron`.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/cron.md'),
);
$items['docs-scripts'] = array(
'description' => 'Shell script overview on writing simple sequences of Drush statements.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/shellscripts.md'),
);
$items['docs-shell-aliases'] = array(
'description' => 'Shell alias overview on creating your own aliases for commonly used Drush commands.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/shellaliases.md'),
);
$items['docs-commands'] = array(
'description' => 'Drush command instructions on creating your own Drush commands.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/commands.md'),
);
$items['docs-errorcodes'] = array(
'description' => 'Error code list containing all identifiers used with drush_set_error.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
);
$items['docs-api'] = array(
'description' => 'Drush API',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/drush.api.php'),
);
$items['docs-context'] = array(
'description' => 'Contexts overview explaining how Drush manages command line options and configuration file settings.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/context.md'),
);
$items['docs-examplescript'] = array(
'description' => 'Example Drush script.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'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_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/sandwich.drush.inc'),
);
$items['docs-example-sync-extension'] = array(
'description' => 'Example Drush commandfile that extends sql-sync to enable development modules in the post-sync hook.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/sync_enable.drush.inc'),
);
$items['docs-example-sync-via-http'] = array(
'description' => 'Example Drush commandfile that extends sql-sync to allow transfer of the sql dump file via http rather than ssh and rsync.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/sync_via_http.drush.inc'),
);
$items['docs-policy'] = array(
'description' => 'Example policy file.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/examples/policy.drush.inc'),
);
$items['docs-strict-options'] = array(
'description' => 'Strict option handling, and how commands that use it differ from regular Drush commands.',
'hidden' => TRUE,
'topic' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'callback' => 'drush_print_file',
'callback arguments' => array($docs_dir . '/docs/strict-options.md'),
);
return $items;
}
/**
* docs-errorcodes 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('@self', '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 (e.g
// updatecode.pm.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);
$handle = fopen($tmpfile, 'a');
drush_print_table($data, FALSE, array(0 => 35), $handle);
fclose($handle);
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,291 @@
<?php
/**
* @file
* Drupal 7 engine for the Batch API
*/
use Drush\Log\LogLevel;
/**
* 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', $args, $options) {
$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.
if (drush_drupal_major_version() >= 8) {
\Drupal::moduleHandler()->alter('batch', $batch);
}
else {
drupal_alter('batch', $batch);
}
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
// table, since non-progressive batches skip database storage completely.
$batch['id'] = db_next_id();
$args[] = $batch['id'];
$batch['progressive'] = TRUE;
// Move operations to a job queue. Non-progressive batches will use a
// memory-based queue.
foreach ($batch['sets'] as $key => $batch_set) {
_batch_populate_queue($batch, $key);
}
drush_include_engine('drupal', 'environment');
// Store the batch.
if (drush_drupal_major_version() >= 8) {
/** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
$batch_storage = \Drupal::service('batch.storage');
$batch_storage->create($batch);
}
else {
db_insert('batch')
->fields(array(
'bid' => $batch['id'],
'timestamp' => REQUEST_TIME,
'token' => drush_get_token($batch['id']),
'batch' => serialize($batch),
))
->execute();
}
$finished = FALSE;
// Not used in D8+ and possibly earlier.
global $user;
while (!$finished) {
$data = drush_invoke_process('@self', $command, $args, array('user' => drush_user_get_class()->getCurrentUserAsSingle()->id()));
$finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
}
}
}
/**
* Initialize the batch command and call the worker function.
*
* Loads the batch record from the database and sets up the requirements
* for the worker, such as registering the shutdown function.
*
* @param id
* The batch id of the batch being processed.
*/
function _drush_batch_command($id) {
$batch =& batch_get();
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(
':bid' => $id))->fetchField();
if ($data) {
$batch = unserialize($data);
}
else {
return FALSE;
}
if (!isset($batch['running'])) {
$batch['running'] = TRUE;
}
// Register database update for end of processing.
register_shutdown_function('_drush_batch_shutdown');
if (_drush_batch_worker()) {
_drush_batch_finished();
}
}
/**
* Process batch operations
*
* Using the current $batch process each of the operations until the batch
* has been completed or half of the available memory for the process has been
* reached.
*/
function _drush_batch_worker() {
$batch =& batch_get();
$current_set =& _batch_current_set();
$set_changed = TRUE;
if (empty($current_set['start'])) {
$current_set['start'] = microtime(TRUE);
}
$queue = _batch_queue($current_set);
while (!$current_set['success']) {
// If this is the first time we iterate this batch set in the current
// request, we check if it requires an additional file for functions
// definitions.
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
include_once DRUPAL_ROOT . '/' . $current_set['file'];
}
$task_message = '';
// Assume a single pass operation and set the completion level to 1 by
// default.
$finished = 1;
if ($item = $queue->claimItem()) {
list($function, $args) = $item->data;
// Build the 'context' array and execute the function call.
$batch_context = array(
'sandbox' => &$current_set['sandbox'],
'results' => &$current_set['results'],
'finished' => &$finished,
'message' => &$task_message,
);
// Magic wrap to catch changes to 'message' key.
$batch_context = new DrushBatchContext($batch_context);
// Tolerate recoverable errors.
// See https://github.com/drush-ops/drush/issues/1930
$halt_on_error = drush_get_option('halt-on-error', TRUE);
drush_set_option('halt-on-error', FALSE);
call_user_func_array($function, array_merge($args, array(&$batch_context)));
drush_set_option('halt-on-error', $halt_on_error);
$finished = $batch_context['finished'];
if ($finished >= 1) {
// Make sure this step is not counted twice when computing $current.
$finished = 0;
// Remove the processed operation and clear the sandbox.
$queue->deleteItem($item);
$current_set['count']--;
$current_set['sandbox'] = array();
}
}
// When all operations in the current batch set are completed, browse
// through the remaining sets, marking them 'successfully processed'
// along the way, until we find a set that contains operations.
// _batch_next_set() executes form submit handlers stored in 'control'
// sets (see form_execute_handlers()), which can in turn add new sets to
// the batch.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set = &_batch_current_set();
$current_set['start'] = microtime(TRUE);
$set_changed = TRUE;
}
// At this point, either $current_set contains operations that need to be
// processed or all sets have been completed.
$queue = _batch_queue($current_set);
// If we are in progressive mode, break processing after 1 second.
if (drush_memory_limit() > 0 && (memory_get_usage() * 2) >= drush_memory_limit()) {
drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::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 (is_callable($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
$elapsed = $batch_set['elapsed'] / 1000;
$elapsed = drush_drupal_major_version() >=8 ? \Drupal::service('date.formatter')->formatInterval($elapsed) : format_interval($elapsed);
$batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, $elapsed);
}
}
}
// Clean up the batch table and unset the static $batch variable.
if (drush_drupal_major_version() >= 8) {
/** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
$batch_storage = \Drupal::service('batch.storage');
$batch_storage->delete($batch['id']);
}
else {
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()) {
if (drush_drupal_major_version() >= 8) {
/** @var \Drupal\Core\Batch\BatchStorage $batch_storage */
$batch_storage = \Drupal::service('batch.storage');
$batch_storage->update($batch);
}
else {
db_update('batch')
->fields(array('batch' => serialize($batch)))
->condition('bid', $batch['id'])
->execute();
}
}
}

View file

@ -0,0 +1,199 @@
<?php
/**
* @file
* Drupal 6 engine for the Batch API
*/
use Drush\Log\LogLevel;
/**
* 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', $args, $options) {
$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');
$args[] = $batch['id'];
// Actually store the batch data and the token generated form the batch id.
db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']);
$finished = FALSE;
while (!$finished) {
$data = drush_invoke_process('@self', $command, $args, $options);
$finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE);
}
}
}
/**
* Initialize the batch command and call the worker function.
*
* Loads the batch record from the database and sets up the requirements
* for the worker, such as registering the shutdown function.
*
* @param id
* The batch id of the batch being processed.
*/
function _drush_batch_command($id) {
$batch =& batch_get();
// Retrieve the current state of batch from db.
if ($data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d", $id))) {
$batch = unserialize($data);
}
else {
return FALSE;
}
if (!isset($batch['running'])) {
$batch['running'] = TRUE;
}
// Register database update for end of processing.
register_shutdown_function('_drush_batch_shutdown');
if (_drush_batch_worker()) {
_drush_batch_finished();
}
}
/**
* Process batch operations
*
* Using the current $batch process each of the operations until the batch
* has been completed or half of the available memory for the process has been
* reached.
*/
function _drush_batch_worker() {
$batch =& batch_get();
$current_set =& _batch_current_set();
$set_changed = TRUE;
timer_start('batch_processing');
while (!$current_set['success']) {
// If this is the first time we iterate this batch set in the current
// request, we check if it requires an additional file for functions
// definitions.
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
include_once($current_set['file']);
}
$finished = 1;
$task_message = '';
if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) {
// Build the 'context' array, execute the function call,
// and retrieve the user message.
$batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message);
// Magic wrap to catch changes to 'message' key.
$batch_context = new DrushBatchContext($batch_context);
// Process the current operation.
call_user_func_array($function, array_merge($args, array(&$batch_context)));
$finished = $batch_context['finished'];
}
if ($finished >= 1) {
// Make sure this step isn't counted double when computing $current.
$finished = 0;
// Remove the operation and clear the sandbox.
array_shift($current_set['operations']);
$current_set['sandbox'] = array();
}
// If the batch set is completed, browse through the remaining sets,
// executing 'control sets' (stored form submit handlers) along the way -
// this might in turn insert new batch sets.
// Stop when we find a set that actually has operations.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set =& _batch_current_set();
$set_changed = TRUE;
}
// At this point, either $current_set is a 'real' batch set (has operations),
// or all sets have been completed.
// TODO - replace with memory check!
// If we're in progressive mode, stop after 1 second.
if ((memory_get_usage() * 2) >= drush_memory_limit()) {
drush_log(dt("Batch process has consumed in excess of 50% of available memory. Starting new thread"), LogLevel::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,101 @@
<?php
/**
* @file
* Engine for the cache commands.
*/
function _drush_cache_command_get($cid, $bin) {
if (is_null($bin)) {
$bin = _drush_cache_bin_default();
}
return cache_get($cid, $bin);
}
/**
* The default bin.
*
* @return string
*/
function _drush_cache_bin_default() {
return 'cache';
}
function _drush_cache_command_set($cid, $data, $bin, $expire, $tags) {
// Convert the "expire" argument to a valid value for Drupal's cache_set().
if (is_null($bin)) {
$bin = _drush_cache_bin_default();
}
if ($expire == 'CACHE_TEMPORARY') {
$expire = CACHE_TEMPORARY;
}
if (!isset($expire) || $expire == 'CACHE_PERMANENT') {
$expire = CACHE_PERMANENT;
}
// D6/D7 don't natively support cache tags.
return cache_set($cid, $data, $bin, $expire);
}
function _drush_cache_clear_types($include_bootstrapped_types) {
$types = array(
'drush' => 'drush_cache_clear_drush',
'all' => 'drush_cache_clear_both',
);
if ($include_bootstrapped_types) {
$types += array(
'theme-registry' => 'drush_cache_clear_theme_registry',
'menu' => 'menu_rebuild',
'css-js' => 'drush_cache_clear_css_js',
'block' => 'drush_cache_clear_block',
'module-list' => 'drush_get_modules',
'theme-list' => 'drush_get_themes',
);
}
$drupal_version = drush_drupal_major_version();
if ($drupal_version >= 7) {
$types['registry'] = 'registry_update';
}
elseif ($drupal_version == 6 && function_exists('module_exists') && module_exists('autoload')) {
// TODO: move this to autoload module.
$types['registry'] = 'autoload_registry_update';
}
return $types;
}
function drush_cache_clear_theme_registry() {
if (drush_drupal_major_version() >= 7) {
drupal_theme_rebuild();
}
else {
cache_clear_all('theme_registry', 'cache', TRUE);
}
}
function drush_cache_clear_menu() {
return menu_router_rebuild();
}
function drush_cache_clear_css_js() {
_drupal_flush_css_js();
drupal_clear_css_cache();
drupal_clear_js_cache();
}
/**
* Clear the cache of the block output.
*/
function drush_cache_clear_block() {
cache_clear_all(NULL, 'cache_block');
}
/**
* Clear caches internal to Drush core and Drupal.
*/
function drush_cache_clear_both() {
drush_cache_clear_drush();
if (drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
drupal_flush_all_caches();
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* @file
* Engine for the cache commands.
*/
use Drupal\Core\Cache\Cache;
function _drush_cache_command_get($cid, $bin) {
if (is_null($bin)) {
$bin = _drush_cache_bin_default();
}
return \Drupal::cache($bin)->get($cid);
}
/**
* The default bin.
*
* @return string
*/
function _drush_cache_bin_default() {
return 'default';
}
function _drush_cache_command_set($cid, $data, $bin, $expire, $tags) {
if (is_null($bin)) {
$bin = _drush_cache_bin_default();
}
// Convert the "expire" argument to a valid value for Drupal's cache_set().
if ($expire == 'CACHE_TEMPORARY') {
$expire = Cache::TEMPORARY;
}
if (!isset($expire) || $expire == 'CACHE_PERMANENT') {
$expire = Cache::PERMANENT;
}
return \Drupal::cache($bin)->set($cid, $data, $expire, $tags);
}
function _drush_cache_clear_types($include_bootstrapped_types) {
$types = array(
'drush' => 'drush_cache_clear_drush',
);
if ($include_bootstrapped_types) {
$types += array(
'theme-registry' => 'drush_cache_clear_theme_registry',
'router' => 'drush_cache_clear_router',
'css-js' => 'drush_cache_clear_css_js',
'module-list' => 'drush_get_modules',
'theme-list' => 'drush_get_themes',
'render' => 'drush_cache_clear_render',
);
}
return $types;
}
function drush_cache_clear_theme_registry() {
\Drupal::service('theme.registry')->reset();
}
function drush_cache_clear_router() {
/** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
$router_builder = \Drupal::service('router.builder');
$router_builder->rebuild();
}
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() {
// There is no distinct block cache in D8. See https://github.com/drush-ops/drush/issues/1531.
// \Drupal::cache('block')->deleteAll();
}
/**
* Clears the render cache entries.
*/
function drush_cache_clear_render() {
Cache::invalidateTags(['rendered']);
}

View file

@ -0,0 +1,438 @@
<?php
/**
* @file
* Specific functions for a drupal 8+ environment.
* drush_include_engine() magically includes either this file
* or environment_X.inc depending on which version of drupal Drush
* is called from.
*/
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PrivateStream;
use Drupal\Core\StreamWrapper\PublicStream;
use Drush\Log\LogLevel;
use Drupal\Core\Logger\RfcLogLevel;
/**
* Get complete information for all available modules.
*
* @param $include_hidden
* Boolean to indicate whether hidden modules should be excluded or not.
* @return
* An array containing module info for all available modules.
*/
function drush_get_modules($include_hidden = TRUE) {
$modules = system_rebuild_module_data();
foreach ($modules as $key => $module) {
if ((!$include_hidden) && (!empty($module->info['hidden']))) {
unset($modules[$key]);
}
else {
$module->schema_version = drupal_get_installed_schema_version($key);
}
}
return $modules;
}
/**
* Returns drupal required modules, including modules declared as required dynamically.
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($module_info as $name => $module) {
if (isset($module->info['required']) && $module->info['required']) {
$required[] = $name;
}
}
return array_unique($required);
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$dependencies = array_reverse($module_info[$module]->requires);
$unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info));
if (!empty($unmet_dependencies)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
else {
// check for version incompatibility
foreach ($dependencies as $dependency_name => $v) {
$current_version = $module_info[$dependency_name]->info['version'];
$current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version);
$incompatibility = drupal_check_incompatibility($v, $current_version);
if (isset($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'] = $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) {
$keys = array_keys($module_info[$module]->required_by);
$dependents = array_merge($dependents, array_combine($keys, $keys));
}
return array_unique($dependents);
}
/**
* Returns a list of enabled modules.
*
* This is a wrapper for module_list().
*/
function drush_module_list() {
$modules = array_keys(\Drupal::moduleHandler()->getModuleList());
return array_combine($modules, $modules);
}
/**
* Installs a given list of modules.
*
* @see \Drupal\Core\Extension\ModuleInstallerInterface::install()
*
*/
function drush_module_install($module_list, $enable_dependencies = TRUE) {
return \Drupal::service('module_installer')->install($module_list, $enable_dependencies);
}
/**
* Checks that a given module exists and is enabled.
*
* @see \Drupal\Core\Extension\ModuleHandlerInterface::moduleExists()
*
*/
function drush_module_exists($module) {
return \Drupal::moduleHandler()->moduleExists($module);
}
/**
* Determines which modules are implementing a hook.
*
* @param string $hook
* The hook name.
* @param bool $sort
* Not used in Drupal 8 environment.
* @param bool $reset
* TRUE to reset the hook implementation cache.
*
* @see \Drupal\Core\Extension\ModuleHandlerInterface::getImplementations().
* @see \Drupal\Core\Extension\ModuleHandlerInterface::resetImplementations().
*
*/
function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) {
// $sort is there for consistency, but looks like Drupal 8 has no equilavient for it.
// We can sort the list manually later if really needed.
if ($reset == TRUE){
\Drupal::moduleHandler()->resetImplementations();
}
return \Drupal::moduleHandler()->getImplementations($hook);
}
/**
* Return a list of modules from a list of named modules.
* Both enabled and disabled/uninstalled modules are returned.
*/
function drush_get_named_extensions_list($extensions) {
$result = array();
$modules = drush_get_modules();
foreach($modules as $name => $module) {
if (in_array($name, $extensions)) {
$result[$name] = $module;
}
}
$themes = drush_get_themes();
foreach($themes as $name => $theme) {
if (in_array($name, $extensions)) {
$result[$name] = $theme;
}
}
return $result;
}
/**
* 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.
drush_module_install($modules);
// Our logger got blown away during the container rebuild above.
$boot = drush_select_bootstrap_class();
$boot->add_logger();
// Flush all caches. No longer needed in D8 per https://github.com/drush-ops/drush/issues/1207
// drupal_flush_all_caches();
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
drush_set_error('DRUSH_MODULE_DISABLE', dt('Drupal 8 does not support disabling modules. Use pm-uninstall instead.'));
}
/**
* Uninstall a list of modules.
*
* @param $modules
* Array of module names
*
* @see \Drupal\Core\Extension\ModuleInstallerInterface::uninstall()
*/
function drush_module_uninstall($modules) {
\Drupal::service('module_installer')->uninstall($modules);
// Our logger got blown away during the container rebuild above.
$boot = drush_select_bootstrap_class();
$boot->add_logger();
}
/**
* Invokes a hook in a particular module.
*
*/
function drush_module_invoke($module, $hook) {
$args = func_get_args();
// Remove $module and $hook from the arguments.
unset($args[0], $args[1]);
return \Drupal::moduleHandler()->invoke($module, $hook, $args);
}
/**
* Invokes a hook in all enabled modules that implement it.
*
*/
function drush_module_invoke_all($hook) {
$args = func_get_args();
// Remove $hook from the arguments.
array_shift($args);
return \Drupal::moduleHandler()->invokeAll($hook, $args);
}
/**
* Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild
* and include hidden as well.
*
* @return \Drupal\Core\Extension\Extension[]
* A list of themes keyed by name.
*/
function drush_theme_list() {
$theme_handler = \Drupal::service('theme_handler');
return $theme_handler->listInfo();
}
/**
* Get complete information for all available themes.
*
* @param $include_hidden
* Boolean to indicate whether hidden themes should be excluded or not.
* @return
* An array containing theme info for all available themes.
*/
function drush_get_themes($include_hidden = TRUE) {
$themes = \Drupal::service('theme_handler')->rebuildThemeData();
foreach ($themes as $key => $theme) {
if (!$include_hidden) {
if (isset($theme->info['hidden'])) {
// Don't exclude default or admin theme.
if ($key != _drush_theme_default() && $key != _drush_theme_admin()) {
unset($themes[$key]);
}
}
}
}
return $themes;
}
/**
* Enable a list of themes.
*
* @param $themes
* Array of theme names.
*/
function drush_theme_enable($themes) {
\Drupal::service('theme_handler')->install($themes);
}
/**
* Disable a list of themes.
*
* @param $themes
* Array of theme names.
*/
function drush_theme_disable($themes) {
drush_set_error('DRUSH_THEME_DISABLE', dt('Drupal 8 does not support disabling themes. Use pm-uninstall instead.'));
}
/**
* Uninstall a list of themes.
*
* @param $themes
* Array of theme names
*
* @see \Drupal\Core\Extension\ThemeHandlerInterface::uninstall()
*/
function drush_theme_uninstall($themes) {
\Drupal::service('theme_handler')->uninstall($themes);
// Our logger got blown away during the container rebuild above.
$boot = drush_select_bootstrap_class();
$boot->add_logger();
}
/**
* Helper function to obtain the severity levels based on Drupal version.
*
* @return array
* Watchdog severity levels keyed by RFC 3164 severities.
*/
function drush_watchdog_severity_levels() {
return array(
RfcLogLevel::EMERGENCY => LogLevel::EMERGENCY,
RfcLogLevel::ALERT => LogLevel::ALERT,
RfcLogLevel::CRITICAL => LogLevel::CRITICAL,
RfcLogLevel::ERROR => LogLevel::ERROR,
RfcLogLevel::WARNING => LogLevel::WARNING,
RfcLogLevel::NOTICE => LogLevel::NOTICE,
RfcLogLevel::INFO => LogLevel::INFO,
RfcLogLevel::DEBUG => LogLevel::DEBUG,
);
}
/**
* Helper function to obtain the message types based on drupal version.
*
* @return
* Array of watchdog message types.
*/
function drush_watchdog_message_types() {
return _dblog_get_message_types();
}
function _drush_theme_default() {
return \Drupal::config('system.theme')->get('default');
}
function _drush_theme_admin() {
$theme = \Drupal::config('system.theme')->get('admin');
return empty($theme) ? 'seven' : $theme;
}
function _drush_file_public_path() {
return PublicStream::basePath();
}
function _drush_file_private_path() {
return PrivateStream::basePath();
}
/**
* Gets the extension name.
*
* @param $info
* The extension info.
* @return string
* The extension name.
*/
function _drush_extension_get_name($info) {
return $info->getName();
}
/**
* Gets the extension type.
*
* @param $info
* The extension info.
* @return string
* The extension type.
*/
function _drush_extension_get_type($info) {
return $info->getType();
}
/**
* Gets the extension path.
*
* @param $info
* The extension info.
* @return string
* The extension path.
*/
function _drush_extension_get_path($info) {
return $info->getPath();
}
/*
* Wrapper for CSRF token generation.
*/
function drush_get_token($value = NULL) {
return \Drupal::csrfToken()->get($value);
}
/*
* Wrapper for _url().
*/
function drush_url($path = NULL, array $options = array()) {
return \Drupal\Core\Url::fromUserInput('/' . $path, $options)->toString();
}
/**
* Output a Drupal render array, object or string as plain text.
*
* @param string $data
* Data to render.
*
* @return string
* The plain-text representation of the input.
*/
function drush_render($data) {
if (is_array($data)) {
$data = \Drupal::service('renderer')->renderRoot($data);
}
$data = \Drupal\Core\Mail\MailFormatHelper::htmlToText($data);
return $data;
}

View file

@ -0,0 +1,416 @@
<?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.
*
* @param $include_hidden
* Boolean to indicate whether hidden modules should be excluded or not.
* @return
* An array containing module info for all available modules.
*/
function drush_get_modules($include_hidden = TRUE) {
$modules = module_rebuild_cache();
foreach ($modules as $key => $module) {
if (!isset($module->type)) {
$module->type = 'module';
}
if ((!$include_hidden) && isset($module->info['hidden']) && ($module->info['hidden'])) {
unset($modules[$key]);
}
}
return $modules;
}
/**
* Returns drupal required modules, including their dependencies.
*
* A module may alter other module's .info to set a dependency on it.
* See for example http://drupal.org/project/phpass
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($required as $module) {
$required = array_merge($required, $module_info[$module]->info['dependencies']);
}
return $required;
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$dependencies = array_reverse($module_info[$module]->info['dependencies']);
$unmet_dependencies = array_diff($dependencies, array_keys($module_info));
if (!empty($unmet_dependencies)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
$status[$key]['unmet-dependencies'] = $unmet_dependencies;
$status[$key]['dependencies'] = array();
foreach ($dependencies as $dependency) {
$status[$key]['dependencies'][$dependency] = array('name' => $dependency);
}
}
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);
}
/**
* Returns a list of enabled modules.
*
* This is a simplified version of module_list().
*/
function drush_module_list() {
$enabled = array();
$rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));
while ($row = drush_db_result($rsc)) {
$enabled[$row] = $row;
}
return $enabled;
}
/**
* Return a list of extensions from a list of named extensions.
* Both enabled and disabled/uninstalled extensions are returned.
*/
function drush_get_named_extensions_list($extensions) {
$result = array();
$rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
while ($row = drush_db_fetch_object($rsc)) {
$result[$row->name] = $row;
}
return $result;
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// Try to install modules previous to enabling.
foreach ($modules as $module) {
_drupal_install_module($module);
}
module_enable($modules);
drush_system_modules_form_submit();
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
module_disable($modules, FALSE);
drush_system_modules_form_submit();
}
/**
* Uninstall a list of modules.
*
* @param $modules
* Array of module names
*/
function drush_module_uninstall($modules) {
require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
foreach ($modules as $module) {
drupal_uninstall_module($module);
}
}
/**
* Checks that a given module exists and is enabled.
*
* @see module_exists().
*
*/
function drush_module_exists($module) {
return module_exists($module);
}
/**
* Determines which modules are implementing a hook.
*
*/
function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) {
return module_implements($hook, $sort, $reset);
}
/**
* Invokes a hook in a particular module.
*
*/
function drush_module_invoke($module, $hook) {
$args = func_get_args();
return call_user_func_array('module_invoke', $args);
}
/**
* Invokes a hook in all enabled modules that implement it.
*
*/
function drush_module_invoke_all($hook) {
$args = func_get_args();
return call_user_func_array('module_invoke_all', $args);
}
/**
* Submit the system modules form.
*
* The modules should already be fully enabled/disabled before calling this
* function. Calling this function just makes sure any activities triggered by
* the form submit (such as admin_role) are completed.
*/
function drush_system_modules_form_submit() {
$active_modules = array();
foreach (drush_get_modules(FALSE) as $key => $module) {
if ($module->status == 1) {
$active_modules[$key] = $key;
}
}
module_load_include('inc', 'system', 'system.admin');
$form_state = array('values' => array('status' => $active_modules));
drupal_execute('system_modules', $form_state);
}
/**
* Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild
* and include hidden/disabled as well.
*
* @return array
* A list of themes keyed by name.
*/
function drush_theme_list() {
$enabled = array();
foreach (list_themes() as $key => $info) {
if ($info->status) {
$enabled[$key] = $info;
}
}
return $enabled;
}
/**
* Get complete information for all available themes.
*
* We need to set the type for those themes that are not already in the system table.
*
* @param $include_hidden
* Boolean to indicate whether hidden themes should be excluded or not.
* @return
* An array containing theme info for all available themes.
*/
function drush_get_themes($include_hidden = TRUE) {
$themes = system_theme_data();
foreach ($themes as $key => $theme) {
if (!isset($theme->type)) {
$theme->type = 'theme';
}
if ((!$include_hidden) && isset($theme->info['hidden']) && ($theme->info['hidden'])) {
unset($themes[$key]);
}
}
return $themes;
}
/**
* Enable a list of themes.
*
* This function is based on system_themes_form_submit().
*
* @see system_themes_form_submit()
* @param $themes
* Array of theme names.
*/
function drush_theme_enable($themes) {
drupal_clear_css_cache();
foreach ($themes as $theme) {
system_initialize_theme_blocks($theme);
}
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes);
list_themes(TRUE);
menu_rebuild();
module_invoke('locale', 'system_update', $themes);
}
/**
* Disable a list of themes.
*
* This function is based on system_themes_form_submit().
*
* @see system_themes_form_submit()
* @param $themes
* Array of theme names.
*/
function drush_theme_disable($themes) {
drupal_clear_css_cache();
db_query("UPDATE {system} SET status = 0 WHERE type = 'theme' AND name IN (".db_placeholders($themes, 'text').")", $themes);
list_themes(TRUE);
menu_rebuild();
drupal_rebuild_theme_registry();
module_invoke('locale', 'system_update', $themes);
}
/**
* Helper function to obtain the severity levels based on Drupal version.
*
* This is a copy of watchdog_severity_levels() without t().
*
* Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
*
* @return
* Array of watchdog severity levels.
*/
function drush_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',
);
}
/**
* Helper function to obtain the message types based on drupal version.
*
* @return
* Array of watchdog message types.
*/
function drush_watchdog_message_types() {
return drupal_map_assoc(_dblog_get_message_types());
}
function _drush_theme_default() {
return variable_get('theme_default', 'garland');
}
function _drush_theme_admin() {
return variable_get('admin_theme', drush_theme_get_default());
}
function _drush_file_public_path() {
if (function_exists('file_directory_path')) {
return file_directory_path();
}
}
function _drush_file_private_path() {
// @todo
}
/**
* Gets the extension name.
*
* @param $info
* The extension info.
* @return string
* The extension name.
*/
function _drush_extension_get_name($info) {
return $info->name;
}
/**
* Gets the extension type.
*
* @param $info
* The extension info.
* @return string
* The extension type.
*/
function _drush_extension_get_type($info) {
return $info->type;
}
/**
* Gets the extension path.
*
* @param $info
* The extension info.
* @return string
* The extension path.
*/
function _drush_extension_get_path($info) {
return dirname($info->filename);
}
/*
* Wrapper for CSRF token generation.
*/
function drush_get_token($value = NULL) {
return drupal_get_token($value);
}
/*
* Wrapper for _url().
*/
function drush_url($path = NULL, $options = array()) {
return url($path, $options);
}
/**
* Output a Drupal render array, object or plain string as plain text.
*
* @param string $data
* Data to render.
*
* @return string
* The plain-text representation of the input.
*/
function drush_render($data) {
if (is_array($data)) {
$data = drupal_render($data);
}
$data = drupal_html_to_text($data);
return $data;
}

View file

@ -0,0 +1,389 @@
<?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.
*
* @param $include_hidden
* Boolean to indicate whether hidden modules should be excluded or not.
* @return
* An array containing module info for all available modules.
*/
function drush_get_modules($include_hidden = TRUE) {
$modules = system_rebuild_module_data();
if (!$include_hidden) {
foreach ($modules as $key => $module) {
if (isset($module->info['hidden'])) {
unset($modules[$key]);
}
}
}
return $modules;
}
/**
* Returns drupal required modules, including modules declared as required dynamically.
*/
function _drush_drupal_required_modules($module_info) {
$required = drupal_required_modules();
foreach ($module_info as $name => $module) {
if (isset($module->info['required']) && $module->info['required']) {
$required[] = $name;
}
}
return array_unique($required);
}
/**
* Return dependencies and its status for modules.
*
* @param $modules
* Array of module names
* @param $module_info
* Drupal 'files' array for modules as returned by drush_get_modules().
* @return
* Array with dependencies and status for $modules
*/
function drush_check_module_dependencies($modules, $module_info) {
$status = array();
foreach ($modules as $key => $module) {
$dependencies = array_reverse($module_info[$module]->requires);
$unmet_dependencies = array_diff(array_keys($dependencies), array_keys($module_info));
if (!empty($unmet_dependencies)) {
$status[$key]['error'] = array(
'code' => 'DRUSH_PM_ENABLE_DEPENDENCY_NOT_FOUND',
'message' => dt('Module !module cannot be enabled because it depends on the following modules which could not be found: !unmet_dependencies', array('!module' => $module, '!unmet_dependencies' => implode(',', $unmet_dependencies)))
);
}
else {
// check for version incompatibility
foreach ($dependencies as $dependency_name => $v) {
$current_version = $module_info[$dependency_name]->info['version'];
$current_version = str_replace(drush_get_drupal_core_compatibility() . '-', '', $current_version);
$incompatibility = drupal_check_incompatibility($v, $current_version);
if (isset($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'] = $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);
}
/**
* Returns a list of enabled modules.
*
* This is a simplified version of module_list().
*/
function drush_module_list() {
$enabled = array();
$rsc = drush_db_select('system', 'name', 'type=:type AND status=:status', array(':type' => 'module', ':status' => 1));
while ($row = drush_db_result($rsc)) {
$enabled[$row] = $row;
}
return $enabled;
}
/**
* Return a list of extensions from a list of named extensions.
* Both enabled and disabled/uninstalled extensions are returned.
*/
function drush_get_named_extensions_list($extensions) {
$result = array();
$rsc = drush_db_select('system', array('name', 'status'), 'name IN (:extensions)', array(':extensions' => $extensions));
while ($row = drush_db_fetch_object($rsc)) {
$result[$row->name] = $row;
}
return $result;
}
/**
* Enable a list of modules. It is assumed the list contains all the dependencies not already enabled.
*
* @param $modules
* Array of module names
*/
function drush_module_enable($modules) {
// The list of modules already have all the dependencies, but they might not
// be in the correct order. Still pass $enable_dependencies = TRUE so that
// Drupal will enable the modules in the correct order.
module_enable($modules);
// Flush all caches.
drupal_flush_all_caches();
}
/**
* Disable a list of modules. It is assumed the list contains all dependents not already disabled.
*
* @param $modules
* Array of module names
*/
function drush_module_disable($modules) {
// The list of modules already have all the dependencies, but they might not
// be in the correct order. Still pass $enable_dependencies = TRUE so that
// Drupal will enable the modules in the correct order.
module_disable($modules);
// Flush all caches.
drupal_flush_all_caches();
}
/**
* Uninstall a list of modules.
*
* @param $modules
* Array of module names
*/
function drush_module_uninstall($modules) {
require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
// Break off 8.x functionality when we get another change.
if (drush_drupal_major_version() >= 8) {
module_uninstall($modules);
}
else {
drupal_uninstall_modules($modules);
}
}
/**
* Checks that a given module exists and is enabled.
*
* @see module_exists().
*
*/
function drush_module_exists($module) {
return module_exists($module);
}
/**
* Determines which modules are implementing a hook.
*
*/
function drush_module_implements($hook, $sort = FALSE, $reset = FALSE) {
return module_implements($hook, $sort, $reset);
}
/**
* Invokes a hook in a particular module.
*
*/
function drush_module_invoke($module, $hook) {
$args = func_get_args();
return call_user_func_array('module_invoke', $args);
}
/**
* Invokes a hook in all enabled modules that implement it.
*
*/
function drush_module_invoke_all($hook) {
$args = func_get_args();
return call_user_func_array('module_invoke_all', $args);
}
/**
* Returns a list of enabled themes. Use drush_get_themes() if you need to rebuild
* and include hidden/disabled as well.
*
* @return array
* A list of themes keyed by name.
*/
function drush_theme_list() {
$enabled = array();
foreach (list_themes() as $key => $info) {
if ($info->status) {
$enabled[$key] = $info;
}
}
return $enabled;
}
/**
* Get complete information for all available themes.
*
* @param $include_hidden
* Boolean to indicate whether hidden themes should be excluded or not.
* @return
* An array containing theme info for all available themes.
*/
function drush_get_themes($include_hidden = TRUE) {
$themes = system_rebuild_theme_data();
if (!$include_hidden) {
foreach ($themes as $key => $theme) {
if (isset($theme->info['hidden'])) {
unset($themes[$key]);
}
}
}
return $themes;
}
/**
* Enable a list of themes.
*
* @param $themes
* Array of theme names.
*/
function drush_theme_enable($themes) {
theme_enable($themes);
}
/**
* Disable a list of themes.
*
* @param $themes
* Array of theme names.
*/
function drush_theme_disable($themes) {
theme_disable($themes);
}
/**
* Helper function to obtain the severity levels based on Drupal version.
*
* This is a copy of watchdog_severity_levels() without t().
*
* Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
*
* @return
* Array of watchdog severity levels.
*/
function drush_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',
);
}
/**
* Helper function to obtain the message types based on drupal version.
*
* @return
* Array of watchdog message types.
*/
function drush_watchdog_message_types() {
return drupal_map_assoc(_dblog_get_message_types());
}
function _drush_theme_default() {
return variable_get('theme_default', 'garland');
}
function _drush_theme_admin() {
return variable_get('admin_theme', drush_theme_get_default());
}
function _drush_file_public_path() {
return variable_get('file_public_path', conf_path() . '/files');
}
function _drush_file_private_path() {
return variable_get('file_private_path', FALSE);
}
/**
* Gets the extension name.
*
* @param $info
* The extension info.
* @return string
* The extension name.
*/
function _drush_extension_get_name($info) {
return $info->name;
}
/**
* Gets the extension type.
*
* @param $info
* The extension info.
* @return string
* The extension type.
*/
function _drush_extension_get_type($info) {
return $info->type;
}
/**
* Gets the extension path.
*
* @param $info
* The extension info.
* @return string
* The extension path.
*/
function _drush_extension_get_path($info) {
return dirname($info->filename);
}
/*
* Wrapper for CSRF token generation.
*/
function drush_get_token($value = NULL) {
return drupal_get_token($value);
}
/*
* Wrapper for _url().
*/
function drush_url($path = NULL, array $options = array()) {
return url($path, $options);
}
/**
* Output a Drupal render array, object or plain string as plain text.
*
* @param string $data
* Data to render.
*
* @return string
* The plain-text representation of the input.
*/
function drush_render($data) {
if (is_array($data)) {
$data = drupal_render($data);
}
$data = drupal_html_to_text($data);
return $data;
}

View file

@ -0,0 +1,43 @@
<?php
use Drush\Log\LogLevel;
/**
* @file
* Specific functions for a Drupal image handling.
* drush_include_engine() magically includes either this file
* or image_X.inc depending on which version of Drupal
* is in use.
*/
function drush_image_styles() {
return \Drupal::entityManager()->getStorage('image_style')->loadMultiple();
}
function drush_image_style_load($style_name) {
return \Drupal::entityManager()->getStorage('image_style')->load($style_name);
}
function drush_image_flush_single($style_name) {
if ($style = drush_image_style_load($style_name)) {
$style->flush();
drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), LogLevel::SUCCESS);
}
}
/*
* Command callback. Create an image derivative.
*
* @param string $style_name
* The name of an image style.
*
* @param string $source
* The path to a source image, relative to Drupal root.
*/
function _drush_image_derive($style_name, $source) {
$image_style = drush_image_style_load($style_name);
$derivative_uri = $image_style->buildUri($source);
if ($image_style->createDerivative($source, $derivative_uri)) {
return $derivative_uri;
}
}

View file

@ -0,0 +1,45 @@
<?php
use Drush\Log\LogLevel;
/**
* @file
* Specific functions for a Drupal image handling.
* drush_include_engine() magically includes either this file
* or image_X.inc depending on which version of Drupal
* is in use.
*/
function drush_image_styles() {
return image_styles();
}
function drush_image_style_load($style_name) {
return image_style_load($style_name);
}
function drush_image_flush_single($style_name) {
if ($style = image_style_load($style_name)) {
image_style_flush($style);
drush_log(dt('Image style !style_name flushed', array('!style_name' => $style_name)), LogLevel::SUCCESS);
}
}
/*
* Command callback. Create an image derivative.
*
* @param string $style_name
* The name of an image style.
*
* @param string $source
* The path to a source image, relative to Drupal root.
*/
function _drush_image_derive($style_name, $source) {
$image_style = image_style_load($style_name);
$scheme = file_default_scheme();
$image_uri = $scheme . '://' . $source;
$derivative_uri = image_style_path($image_style['name'], $image_uri);
if (image_style_create_derivative($image_style, $source, $derivative_uri)) {
return $derivative_uri;
}
}

View file

@ -0,0 +1,162 @@
<?php
use Drush\Log\LogLevel;
/**
* Command callback. Disable one or more extensions.
*/
function _drush_pm_disable($args) {
$extension_info = drush_get_extensions();
// classify $args in themes, modules or unknown.
$modules = array();
$themes = array();
drush_pm_classify_extensions($args, $modules, $themes, $extension_info);
$extensions = array_merge($modules, $themes);
$unknown = array_diff($args, $extensions);
// Discard and set an error for each unknown extension.
foreach ($unknown as $name) {
drush_log('DRUSH_PM_ENABLE_EXTENSION_NOT_FOUND', dt('!extension was not found and will not be disabled.', array('!extension' => $name)), LogLevel::WARNING);
}
// Discard already disabled extensions.
foreach ($extensions as $name) {
if (!$extension_info[$name]->status) {
if ($extension_info[$name]->type == 'module') {
unset($modules[$name]);
}
else {
unset($themes[$name]);
}
drush_log(dt('!extension is already disabled.', array('!extension' => $name)), LogLevel::OK);
}
}
// Discard default theme.
if (!empty($themes)) {
$default_theme = drush_theme_get_default();
if (in_array($default_theme, $themes)) {
unset($themes[$default_theme]);
drush_log(dt('!theme is the default theme and can\'t be disabled.', array('!theme' => $default_theme)), LogLevel::OK);
}
}
if (!empty($modules)) {
// Add enabled dependents to the list of modules to disable.
$dependents = drush_module_dependents($modules, $extension_info);
$dependents = array_intersect($dependents, drush_module_list());
$modules = array_merge($modules, $dependents);
// Discard required modules.
$required = drush_drupal_required_modules($extension_info);
foreach ($required as $module) {
if (isset($modules[$module])) {
unset($modules[$module]);
$info = $extension_info[$module]->info;
// No message for hidden modules.
if (!isset($info['hidden'])) {
$explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation.', array('!explanation' => strip_tags($info['explanation']))) : '';
drush_log(dt('!module is a required module and can\'t be disabled.', array('!module' => $module)) . $explanation, LogLevel::OK);
}
}
}
}
// Inform the user which extensions will finally be disabled.
$extensions = array_merge($modules, $themes);
if (empty($extensions)) {
return drush_log(dt('There were no extensions that could be disabled.'), LogLevel::OK);
}
else {
drush_print(dt('The following extensions will be disabled: !extensions', array('!extensions' => implode(', ', $extensions))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Disable themes.
if (!empty($themes)) {
drush_theme_disable($themes);
}
// Disable modules and pass dependency validation in form submit.
if (!empty($modules)) {
drush_module_disable($modules);
}
// Inform the user of final status.
$result_extensions = drush_get_named_extensions_list($extensions);
$problem_extensions = array();
foreach ($result_extensions as $extension) {
if (!$extension->status) {
drush_log(dt('!extension was disabled successfully.', array('!extension' => $extension->name)), LogLevel::OK);
}
else {
$problem_extensions[] = $extension->name;
}
}
if (!empty($problem_extensions)) {
return drush_set_error('DRUSH_PM_DISABLE_EXTENSION_ISSUE', dt('There was a problem disabling !extension.', array('!extension' => implode(',', $problem_extensions))));
}
}
/**
* Command callback. Uninstall one or more modules.
*/
function _drush_pm_uninstall($modules) {
drush_include_engine('drupal', 'environment');
$module_info = drush_get_modules();
$required = drush_drupal_required_modules($module_info);
// Discards modules which are enabled, not found or already uninstalled.
foreach ($modules as $key => $module) {
if (!isset($module_info[$module])) {
// The module does not exist in the system.
unset($modules[$key]);
drush_log(dt('Module !module was not found and will not be uninstalled.', array('!module' => $module)), LogLevel::WARNING);
}
else if ($module_info[$module]->status) {
// The module is enabled.
unset($modules[$key]);
drush_log(dt('!module is not disabled. Use `pm-disable` command before `pm-uninstall`.', array('!module' => $module)), LogLevel::WARNING);
}
else if ($module_info[$module]->schema_version == -1) { // SCHEMA_UNINSTALLED
// The module is uninstalled.
unset($modules[$key]);
drush_log(dt('!module is already uninstalled.', array('!module' => $module)), LogLevel::OK);
}
else {
$dependents = array();
foreach (drush_module_dependents(array($module), $module_info) as $dependent) {
if (!in_array($dependent, $required) && ($module_info[$dependent]->schema_version != -1)) {
$dependents[] = $dependent;
}
}
if (count($dependents)) {
drush_log(dt('To uninstall !module, the following modules must be uninstalled first: !required', array('!module' => $module, '!required' => implode(', ', $dependents))), LogLevel::ERROR);
unset($modules[$key]);
}
}
}
// Inform the user which modules will finally be uninstalled.
if (empty($modules)) {
return drush_log(dt('There were no modules that could be uninstalled.'), LogLevel::OK);
}
else {
drush_print(dt('The following modules will be uninstalled: !modules', array('!modules' => implode(', ', $modules))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Uninstall the modules.
drush_module_uninstall($modules);
// Inform the user of final status.
foreach ($modules as $module) {
drush_log(dt('!module was successfully uninstalled.', array('!module' => $module)), LogLevel::OK);
}
}

View file

@ -0,0 +1,86 @@
<?php
use Drush\Log\LogLevel;
/**
* Command callback. Drupal 8 does not support disabled modules.
*
* @param array $args
* Arguments from the command line.
*/
function _drush_pm_disable($args) {
drush_include_engine('drupal', 'environment');
// To be consistent call the environment.inc function which will show the user
// an error.
drush_module_disable($args);
}
/**
* Command callback. Uninstall one or more extensions.
*
* @param array $args
* Arguments from the command line.
*/
function _drush_pm_uninstall($extensions) {
$extension_info = drush_get_extensions();
$required = drush_drupal_required_modules($extension_info);
// Discards extensions which are enabled, not found or already uninstalled.
$extensions = array_combine($extensions, $extensions);
foreach ($extensions as $extension) {
if (!isset($extension_info[$extension])) {
unset($extensions[$extension]);
drush_log(dt('Extension !extension was not found and will not be uninstalled.', array('!extension' => $extension)), LogLevel::WARNING);
}
elseif (in_array($extension, $required)) {
unset($extensions[$extension]);
$info = $extension_info[$extension]->info;
$explanation = !empty($info['explanation']) ? ' ' . dt('Reason: !explanation.', array('!explanation' => strip_tags($info['explanation']))) : '';
drush_log(dt('!extension is a required extension and can\'t be uninstalled.', array('!extension' => $extension)) . $explanation, LogLevel::OK);
}
elseif (!$extension_info[$extension]->status) {
unset($extensions[$extension]);
drush_log(dt('!extension is already uninstalled.', array('!extension' => $extension)), LogLevel::OK);
}
elseif (drush_extension_get_type($extension_info[$extension]) == 'module') {
// Add installed dependencies to the list of modules to uninstall.
foreach (drush_module_dependents(array($extension), $extension_info) as $dependent) {
// Check if this dependency is not required, already enabled, and not already already in the list of modules to uninstall.
if (!in_array($dependent, $required) && ($extension_info[$dependent]->status) && !in_array($dependent, $extensions)) {
$extensions[] = $dependent;
}
}
}
}
// Discard default theme.
$default_theme = drush_theme_get_default();
if (in_array($default_theme, $extensions)) {
unset($extensions[$default_theme]);
drush_log(dt('!theme is the default theme and can\'t be uninstalled.', array('!theme' => $default_theme)), LogLevel::OK);
}
// Inform the user which extensions will finally be disabled.
if (empty($extensions)) {
return drush_log(dt('There were no extensions that could be uninstalled.'), LogLevel::OK);
}
else {
drush_print(dt('The following extensions will be uninstalled: !extensions', array('!extensions' => implode(', ', $extensions))));
if(!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Classify extensions in themes and modules.
$modules = array();
$themes = array();
drush_pm_classify_extensions($extensions, $modules, $themes, $extension_info);
drush_module_uninstall($modules);
drush_theme_uninstall($themes);
// Inform the user of final status.
foreach ($extensions as $extension) {
drush_log(dt('!extension was successfully uninstalled.', array('!extension' => $extension)), LogLevel::OK);
}
}

View file

@ -0,0 +1,90 @@
<?php
use Drush\Log\LogLevel;
/**
* Install Drupal 8+
*/
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
require_once DRUSH_DRUPAL_CORE . '/includes/install.core.inc';
$class_loader = drush_drupal_load_autoloader(DRUPAL_ROOT);
if (!isset($profile)) {
// If there is an installation profile that acts as a distribution, use that
// one.
$install_state = array('interactive' => FALSE) + install_state_defaults();
try {
install_begin_request($class_loader, $install_state);
$profile = _install_select_profile($install_state);
}
catch (\Exception $e) {
// This is only a best effort to provide a better default, no harm done
// if it fails.
}
if (empty($profile)) {
$profile = 'standard';
}
}
$sql = drush_sql_get_class();
$db_spec = $sql->db_spec();
$account_name = drush_get_option('account-name', 'admin');
$account_pass = drush_get_option('account-pass', FALSE);
$show_password = drush_get_option('show-passwords', !$account_pass);
if (!$account_pass) {
$account_pass = drush_generate_password();
}
$settings = array(
'parameters' => array(
'profile' => $profile,
'langcode' => drush_get_option('locale', 'en'),
),
'forms' => array(
'install_settings_form' => array(
'driver' => $db_spec['driver'],
$db_spec['driver'] => $db_spec,
'op' => dt('Save and continue'),
),
'install_configure_form' => array(
'site_name' => drush_get_option('site-name', 'Site-Install'),
'site_mail' => drush_get_option('site-mail', 'admin@example.com'),
'account' => array(
'name' => $account_name,
'mail' => drush_get_option('account-mail', 'admin@example.com'),
'pass' => array(
'pass1' => $account_pass,
'pass2' => $account_pass,
),
),
'enable_update_status_module' => TRUE,
'enable_update_status_emails' => 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;
}
$msg = 'Starting Drupal installation. This takes a while.';
if (is_null(drush_get_option('notify'))) {
$msg .= ' Consider using the --notify global option.';
}
drush_log(dt($msg), LogLevel::OK);
drush_op('install_drupal', $class_loader, $settings);
if ($show_password) {
drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK);
}
else {
drush_log(dt('Installation complete.'), LogLevel::OK);
}
}

View file

@ -0,0 +1,149 @@
<?php
use Drush\Log\LogLevel;
/**
* Install Drupal 6.x
*/
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
drush_log(dt('Starting Drupal installation. This takes a few seconds ...'), LogLevel::OK);
if (!isset($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_name = drush_get_option('account-name', 'admin');
$account_pass = drush_get_option('account-pass', FALSE);
$show_password = drush_get_option('show-passwords', !$account_pass);
if (!$account_pass) {
$account_pass = drush_generate_password();
}
$phpcode = _drush_site_install6_cookies($profile, $cli_cookie);
$post = array (
"site_name" => drush_get_option('site-name', 'Site-Install'),
"site_mail" => drush_get_option('site-mail', 'admin@example.com'),
"account" => array (
"name" => $account_name,
"mail" => drush_get_option('account-mail', 'admin@example.com'),
"pass" => array (
"pass1" => $account_pass,
"pass2" => $account_pass,
)
),
"date_default_timezone" => "0",
"clean_url" => drush_get_option('clean-url', TRUE),
"form_id" => "install_configure_form",
"update_status_module" => array("1" => "1"),
);
// Merge in the additional options.
foreach ($additional_form_options as $key => $value) {
$current = &$post;
foreach (explode('.', $key) as $param) {
$current = &$current[$param];
}
$current = $value;
}
$phpcode .= '
$_POST = ' . var_export($post, true) . ';
include("'. $drupal_root .'/install.php");';
drush_shell_exec('php -r %s', $phpcode);
if ($show_password) {
drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK);
}
else {
drush_log(dt('Installation complete.'), LogLevel::OK);
}
}
/**
* Submit a given op to install.php; if a meta "Refresh" tag
* is returned in the result, then submit that op as well.
*/
function _drush_site_install6_stage($profile, $cli_cookie, $initial_op) {
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
// Remember the install task at the start of the stage
$install_task = _drush_site_install6_install_task();
$op = $initial_op;
while (!empty($op)) {
$phpcode = _drush_site_install6_cookies($profile, $cli_cookie). ' $_GET["op"]="' . $op . '"; include("'. $drupal_root .'/install.php");';
drush_shell_exec('php -r %s', $phpcode);
$output = implode("\n", drush_shell_exec_output());
// Check for a "Refresh" back to the do_nojs op; e.g.:
// <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_context('DRUSH_SELECTED_URI'));
$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,93 @@
<?php
use Drush\Log\LogLevel;
/**
* Install Drupal 7
*/
function drush_core_site_install_version($profile, array $additional_form_options = array()) {
require_once DRUSH_DRUPAL_CORE . '/includes/install.core.inc';
if (!isset($profile)) {
require_once DRUSH_DRUPAL_CORE . '/includes/file.inc';
require_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
require_once DRUSH_DRUPAL_CORE . '/includes/common.inc';
require_once DRUSH_DRUPAL_CORE . '/includes/module.inc';
// If there is an installation profile that is marked as exclusive, use that
// one.
try {
$profile = _install_select_profile(install_find_profiles());
}
catch (\Exception $e) {
// This is only a best effort to provide a better default, no harm done
// if it fails.
}
if (empty($profile)) {
$profile = 'standard';
}
}
$sql = drush_sql_get_class();
$db_spec = $sql->db_spec();
$account_name = drush_get_option('account-name', 'admin');
$account_pass = drush_get_option('account-pass', FALSE);
$show_password = drush_get_option('show-passwords', !$account_pass);
if (!$account_pass) {
$account_pass = drush_generate_password();
}
$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' => $account_name,
'mail' => drush_get_option('account-mail', 'admin@example.com'),
'pass' => array(
'pass1' => $account_pass,
'pass2' => $account_pass,
),
),
'update_status_module' => array(
1 => TRUE,
2 => TRUE,
),
'clean_url' => drush_get_option('clean-url', TRUE),
'op' => dt('Save and continue'),
),
),
);
// Merge in the additional options.
foreach ($additional_form_options as $key => $value) {
$current = &$settings['forms'];
foreach (explode('.', $key) as $param) {
$current = &$current[$param];
}
$current = $value;
}
$msg = 'Starting Drupal installation. This takes a while.';
if (is_null(drush_get_option('notify'))) {
$msg .= ' Consider using the --notify global option.';
}
drush_log(dt($msg), LogLevel::OK);
drush_op('install_drupal', $settings);
if ($show_password) {
drush_log(dt('Installation complete. User name: @name User password: @pass', array('@name' => $account_name, '@pass' => $account_pass)), LogLevel::OK);
}
else {
drush_log(dt('Installation complete.'), LogLevel::OK);
}
}

View file

@ -0,0 +1,406 @@
<?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.
*/
use Drush\Log\LogLevel;
/**
* 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 DRUSH_DRUPAL_CORE . '/includes/update.inc';
use Drupal\Core\Utility\Error;
use Drupal\Core\Entity\EntityStorageException;
/**
* 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;
\Drupal::moduleHandler()->loadInclude($module, 'install');
$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);
}
}
else {
$ret['#abort'] = array('success' => FALSE);
drush_set_error('DRUSH_UPDATE_FUNCTION_NOT_FOUND', dt('Update function @function not found', array('@function' => $function)));
}
if (isset($context['sandbox']['#finished'])) {
$context['finished'] = $context['sandbox']['#finished'];
unset($context['sandbox']['#finished']);
}
if (!isset($context['results'][$module])) {
$context['results'][$module] = array();
}
if (!isset($context['results'][$module][$number])) {
$context['results'][$module][$number] = array();
}
$context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
if (!empty($ret['#abort'])) {
// Record this function in the list of updates that were aborted.
$context['results']['#abort'][] = $function;
}
// Record the schema update if it was completed successfully.
if ($context['finished'] == 1 && empty($ret['#abort'])) {
drupal_set_installed_schema_version($module, $number);
}
$context['message'] = 'Performing ' . $function;
}
/**
* Clears caches and rebuilds the container.
*
* This is called in between regular updates and post updates. Do not use
* drush_drupal_cache_clear_all() as the cache clearing and container rebuild
* must happen in the same process that the updates are run in.
*
* Drupal core's update.php uses drupal_flush_all_caches() directly without
* explicitly rebuilding the container as the container is rebuilt on the next
* HTTP request of the batch.
*
* @see drush_drupal_cache_clear_all()
* @see \Drupal\system\Controller\DbUpdateController::triggerBatch()
*/
function drush_update_cache_rebuild() {
drupal_flush_all_caches();
\Drupal::service('kernel')->rebuildContainer();
}
function update_main() {
// In D8, we expect to be in full bootstrap.
drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL);
require_once DRUPAL_ROOT . '/core/includes/install.inc';
require_once DRUPAL_ROOT . '/core/includes/update.inc';
drupal_load_updates();
update_fix_compatibility();
// Pending hook_update_N() implementations.
$pending = update_get_update_list();
// Pending hook_post_update_X() implementations.
$post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateInformation();
$start = array();
$change_summary = [];
if (drush_get_option('entity-updates', FALSE)) {
$change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary();
}
// Print a list of pending updates for this module and get confirmation.
if (count($pending) || count($change_summary) || count($post_updates)) {
drush_print(dt('The following updates are pending:'));
drush_print();
foreach ($change_summary as $entity_type_id => $changes) {
drush_print($entity_type_id . ' entity type : ');
foreach ($changes as $change) {
drush_print(strip_tags($change), 2);
}
}
foreach (array('update', 'post_update') as $update_type) {
$updates = $update_type == 'update' ? $pending : $post_updates;
foreach ($updates as $module => $updates) {
if (isset($updates['start'])) {
drush_print($module . ' module : ');
if (!empty($updates['pending'])) {
$start += [$module => array()];
$start[$module] = array_merge($start[$module], $updates['pending']);
foreach ($updates['pending'] as $update) {
drush_print(strip_tags($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"), LogLevel::SUCCESS);
}
return count($pending) + count($change_summary) + count($post_updates);
}
function _update_batch_command($id) {
// In D8, we expect to be in full bootstrap.
drush_bootstrap_to_phase(DRUSH_BOOTSTRAP_DRUPAL_FULL);
drush_batch_command($id);
}
/**
* Start the database update batch process.
*/
function drush_update_batch() {
$start = drush_get_update_list();
// 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]));
}
}
// Apply post update hooks.
$post_updates = \Drupal::service('update.post_update_registry')->getPendingUpdateFunctions();
if ($post_updates) {
$operations[] = ['drush_update_cache_rebuild', []];
foreach ($post_updates as $function) {
$operations[] = ['update_invoke_post_update', [$function]];
}
}
// Lastly, perform entity definition updates, which will update storage
// schema if needed. If module update functions need to work with specific
// entity schema they should call the entity update service for the specific
// update themselves.
// @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate()
// @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate()
if (drush_get_option('entity-updates', FALSE) && \Drupal::entityDefinitionUpdateManager()->needsUpdates()) {
$operations[] = array('drush_update_entity_definitions', array());
}
$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);
\Drupal::service('state')->set('system.maintenance_mode', TRUE);
drush_backend_batch_process('updatedb-batch-process');
\Drupal::service('state')->set('system.maintenance_mode', FALSE);
}
/**
* Apply entity schema updates.
*/
function drush_update_entity_definitions(&$context) {
try {
\Drupal::entityDefinitionUpdateManager()->applyUpdates();
}
catch (EntityStorageException $e) {
watchdog_exception('update', $e);
$variables = Error::decodeException($e);
unset($variables['backtrace']);
// The exception message is run through
// \Drupal\Component\Utility\SafeMarkup::checkPlain() by
// \Drupal\Core\Utility\Error::decodeException().
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
$context['results']['core']['update_entity_definitions'] = $ret;
$context['results']['#abort'][] = 'update_entity_definitions';
}
}
// Copy of protected \Drupal\system\Controller\DbUpdateController::getModuleUpdates.
function drush_get_update_list() {
$return = array();
$updates = update_get_update_list();
foreach ($updates as $module => $update) {
$return[$module] = $update['start'];
}
return $return;
}
/**
* Process and display any returned update output.
*
* @see \Drupal\system\Controller\DbUpdateController::batchFinished()
* @see \Drupal\system\Controller\DbUpdateController::results()
*/
function drush_update_finished($success, $results, $operations) {
if (!drush_get_option('cache-clear', TRUE)) {
drush_log(dt("Skipping cache-clear operation due to --cache-clear=0 option."), LogLevel::WARNING);
}
else {
drupal_flush_all_caches();
}
foreach ($results as $module => $updates) {
if ($module != '#abort') {
foreach ($updates as $number => $queries) {
foreach ($queries as $query) {
// If there is no message for this update, don't show anything.
if (empty($query['query'])) {
continue;
}
if ($query['success']) {
drush_log(strip_tags($query['query']));
}
else {
drush_set_error(dt('Failed: ') . strip_tags($query['query']));
}
}
}
}
}
}
/**
* Return a 2 item array with
* - an array where each item is a 3 item associative array describing a pending update.
* - an array listing the first update to run, keyed by module.
*/
function updatedb_status() {
$pending = update_get_update_list();
$return = array();
// Ensure system module's updates run first.
$start['system'] = array();
foreach (\Drupal::entityDefinitionUpdateManager()->getChangeSummary() as $entity_type_id => $changes) {
foreach ($changes as $change) {
$return[] = array(
'module' => dt('@type entity type', array('@type' => $entity_type_id)), 'update_id' => '', 'description' => strip_tags($change));
}
}
// Print a list of pending updates for this module and get confirmation.
foreach ($pending as $module => $updates) {
if (isset($updates['start'])) {
foreach ($updates['pending'] as $update_id => $description) {
// Strip cruft from front.
$description = str_replace($update_id . ' - ', '', $description);
$return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description);
}
if (isset($updates['start'])) {
$start[$module] = $updates['start'];
}
}
}
return array($return, $start);
}
/**
* Apply pending entity schema updates.
*/
function entity_updates_main() {
$change_summary = \Drupal::entityDefinitionUpdateManager()->getChangeSummary();
if (!empty($change_summary)) {
drush_print(dt('The following updates are pending:'));
drush_print();
foreach ($change_summary as $entity_type_id => $changes) {
drush_print($entity_type_id . ' entity type : ');
foreach ($changes as $change) {
drush_print(strip_tags($change), 2);
}
}
if (!drush_confirm(dt('Do you wish to run all pending updates?'))) {
return drush_user_abort();
}
$operations[] = array('drush_update_entity_definitions', array());
$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);
\Drupal::service('state')->set('system.maintenance_mode', TRUE);
drush_backend_batch_process('updatedb-batch-process');
\Drupal::service('state')->set('system.maintenance_mode', FALSE);
}
else {
drush_log(dt("No entity schema updates required"), LogLevel::SUCCESS);
}
}

View file

@ -0,0 +1,522 @@
<?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.
*/
use Drush\Log\LogLevel;
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 (!isset($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 (!isset($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)), LogLevel::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'] != drush_get_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, LogLevel::WARNING);
}
}
}
}
/**
* Create the batch table.
*
* This is part of the Drupal 5.x to 6.x migration.
*/
function update_create_batch_table() {
// If batch table exists, update is not necessary
if (db_table_exists('batch')) {
return;
}
$schema['batch'] = array(
'fields' => array(
'bid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
'token' => array('type' => 'varchar', 'length' => 64, 'not null' => TRUE),
'timestamp' => array('type' => 'int', 'not null' => TRUE),
'batch' => array('type' => 'text', 'not null' => FALSE, 'size' => 'big')
),
'primary key' => array('bid'),
'indexes' => array('token' => array('token')),
);
$ret = array();
db_create_table($ret, 'batch', $schema['batch']);
return $ret;
}
function update_main_prepare() {
global $profile;
// Some unavoidable errors happen because the database is not yet up-to-date.
// Our custom error handler is not yet installed, so we just suppress them.
drush_errors_off();
require_once './includes/bootstrap.inc';
// Minimum load of components.
// This differs from the Drupal 6 update.php workflow for compatbility with
// the Drupal 6 backport of module_implements() caching.
// @see http://drupal.org/node/557542
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
require_once './includes/install.inc';
require_once './includes/file.inc';
require_once './modules/system/system.install';
// Load module basics.
include_once './includes/module.inc';
$module_list['system']['filename'] = 'modules/system/system.module';
$module_list['filter']['filename'] = 'modules/filter/filter.module';
module_list(TRUE, FALSE, FALSE, $module_list);
module_implements('', FALSE, TRUE);
drupal_load('module', 'system');
drupal_load('module', 'filter');
// Set up $language, since the installer components require it.
drupal_init_language();
// Set up theme system for the maintenance page.
drupal_maintenance_theme();
// Check the update requirements for Drupal.
update_check_requirements();
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
$profile = variable_get('install_profile', 'default');
// Updates only run reliably if user ID #1 is logged in. For example, node_delete() requires elevated perms in D5/6.
if (!drush_get_context('DRUSH_USER')) {
drush_set_option('user', 1);
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
}
// This must happen *after* drupal_bootstrap(), since it calls
// variable_(get|set), which only works after a full bootstrap.
_drush_log_update_sql(update_create_batch_table());
// Turn error reporting back on. From now on, only fatal errors (which are
// not passed through the error handler) will cause a message to be printed.
drush_errors_on();
// Perform Drupal 5.x to 6.x updates that are required for update.php to function properly.
_drush_log_update_sql(update_fix_d6_requirements());
// Must unset $theme->status in order to safely rescan and repopulate
// the system table to ensure we have a full picture of the platform.
// This is needed because $theme->status is set to 0 in a call to
// list_themes() done by drupal_maintenance_theme().
// It is a issue with _system_theme_data() that returns its own cache
// variable and can be modififed by others. When this is fixed in
// drupal core we can remove this unset.
// For reference see: http://drupal.org/node/762754
$themes = _system_theme_data();
foreach ($themes as $theme) {
unset($theme->status);
}
drush_get_extensions();
include_once './includes/batch.inc';
drupal_load_updates();
// Disable anything in the {system} table that is not compatible with the current version of Drupal core.
_drush_log_update_sql(update_fix_compatibility());
}
function update_main() {
update_main_prepare();
list($pending, $start) = updatedb_status();
// Print a list of pending updates for this module and get confirmation.
if ($pending) {
// @todo get table header working
// array_unshift($pending, array(dt('Module'), dt('ID'), dt('Description')));
drush_print_table($pending, FALSE);
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)), LogLevel::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"), LogLevel::SUCCESS);
}
return count($pending);
}
/**
* 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", LogLevel::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);
}
/**
* Return a 2 item array with
* - an array where each item is a 3 item associative array describing a pending update.
* - an array listing the first update to run, keyed by module.
*/
function updatedb_status() {
$return = array();
$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);
// Record the starting update number for each module.
foreach (array_keys($updates) as $update) {
if ($update > $schema_version) {
$start[$module] = $update;
break;
}
}
if (isset($start['system'])) {
// Ensure system module's updates run first.
$start = array('system' => $start['system']) + $start;
}
// 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 = trim(str_replace(array("\n", '*', '/'), '', $func->getDocComment()));
}
if (empty($description)) {
$description = dt('description not available');
}
$return[] = array('module' => ucfirst($module), 'update_id' => $update, 'description' => $description);
}
}
}
}
return array($return, $start);
}

View file

@ -0,0 +1,339 @@
<?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.
*/
use Drush\Log\LogLevel;
/**
* 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 DRUSH_DRUPAL_CORE . '/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']);
// If the update hook returned a status message (common in batch updates),
// show it to the user.
if ($ret['results']['query']) {
drush_log($ret['results']['query'], LogLevel::OK);
}
$ret['results']['success'] = TRUE;
}
// @TODO We may want to do different error handling for different exception
// types, but for now we'll just print the message.
catch (Exception $e) {
$ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage());
drush_set_error('DRUPAL_EXCEPTION', $e->getMessage());
}
if ($context['log']) {
$ret['queries'] = Database::getLog($function);
}
}
if (isset($context['sandbox']['#finished'])) {
$context['finished'] = $context['sandbox']['#finished'];
unset($context['sandbox']['#finished']);
}
if (!isset($context['results'][$module])) {
$context['results'][$module] = array();
}
if (!isset($context['results'][$module][$number])) {
$context['results'][$module][$number] = array();
}
$context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
if (!empty($ret['#abort'])) {
// Record this function in the list of updates that were aborted.
$context['results']['#abort'][] = $function;
}
// Record the schema update if it was completed successfully.
if ($context['finished'] == 1 && empty($ret['#abort'])) {
drupal_set_installed_schema_version($module, $number);
}
$context['message'] = 'Performed update: ' . $function;
}
/**
* Check update requirements and report any errors.
*/
function update_check_requirements() {
$warnings = FALSE;
// Check the system module and update.php requirements only.
$requirements = system_requirements('update');
$requirements += update_extra_requirements();
// If there are issues, report them.
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] > REQUIREMENT_OK) {
$message = isset($requirement['description']) ? $requirement['description'] : '';
if (isset($requirement['value']) && $requirement['value']) {
$message .= ' (Currently using ' . $requirement['title'] . ' ' . $requirement['value'] . ')';
}
$warnings = TRUE;
drupal_set_message($message, LogLevel::WARNING);
}
}
return $warnings;
}
function update_main_prepare() {
// Some unavoidable errors happen because the database is not yet up-to-date.
// Our custom error handler is not yet installed, so we just suppress them.
drush_errors_off();
// We prepare a minimal bootstrap for the update requirements check to avoid
// reaching the PHP memory limit.
$core = DRUSH_DRUPAL_CORE;
require_once $core . '/includes/bootstrap.inc';
require_once $core . '/includes/common.inc';
require_once $core . '/includes/file.inc';
require_once $core . '/includes/entity.inc';
include_once $core . '/includes/unicode.inc';
update_prepare_d7_bootstrap();
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
require_once $core . '/includes/install.inc';
require_once $core . '/modules/system/system.install';
// Load module basics.
include_once $core . '/includes/module.inc';
$module_list['system']['filename'] = 'modules/system/system.module';
module_list(TRUE, FALSE, FALSE, $module_list);
drupal_load('module', 'system');
// Reset the module_implements() cache so that any new hook implementations
// in updated code are picked up.
module_implements('', FALSE, TRUE);
// Set up $language, since the installer components require it.
drupal_language_initialize();
// Set up theme system for the maintenance page.
drupal_maintenance_theme();
// Check the update requirements for Drupal.
update_check_requirements();
// update_fix_d7_requirements() needs to run before bootstrapping beyond path.
// So bootstrap to DRUPAL_BOOTSTRAP_LANGUAGE then include unicode.inc.
drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
update_fix_d7_requirements();
// Clear the module_implements() cache before the full bootstrap. The calls
// above to drupal_maintenance_theme() and update_check_requirements() have
// invoked hooks before all modules have actually been loaded by the full
// bootstrap. This means that the module_implements() results for any hooks
// that have been invoked, including hook_module_implements_alter(), is a
// smaller set of modules than should be returned normally.
// @see https://github.com/drush-ops/drush/pull/399
module_implements('', FALSE, TRUE);
// 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();
list($pending, $start) = updatedb_status();
if ($pending) {
// @todo get table header working.
// $headers = array(dt('Module'), dt('ID'), dt('Description'));
drush_print_table($pending);
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"), LogLevel::SUCCESS);
}
return count($pending);
}
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.
}
/**
* Return a 2 item array with
* - an array where each item is a 3 item associative array describing a pending update.
* - an array listing the first update to run, keyed by module.
*/
function updatedb_status() {
$pending = update_get_update_list();
$return = array();
// Ensure system module's updates run first.
$start['system'] = array();
// Print a list of pending updates for this module and get confirmation.
foreach ($pending as $module => $updates) {
if (isset($updates['start'])) {
foreach ($updates['pending'] as $update_id => $description) {
// Strip cruft from front.
$description = str_replace($update_id . ' - ', '', $description);
$return[] = array('module' => ucfirst($module), 'update_id' => $update_id, 'description' => $description);
}
if (isset($updates['start'])) {
$start[$module] = $updates['start'];
}
}
}
return array($return, $start);
}

View file

@ -0,0 +1,387 @@
<?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+'),
'arguments' => array(
'bundle' => 'Content type (for nodes). Name of bundle to attach fields to. Required.',
'field_spec' => 'Comma delimited triple in the form: field_name,field_type,widget_name. If widget_name is omitted, the default widget will be used. Separate multiple fields by space. If omitted, a wizard will prompt you.'
),
'required-arguments' => 1,
'options' => array(
'entity_type' => 'Type of entity (e.g. node, user, comment). Defaults to node.',
),
'examples' => array(
'drush field-create article' => 'Define new article fields via interactive prompts.',
'open `drush field-create article`' => 'Define new article fields and then open field edit form for refinement.',
'drush field-create article city,text,text_textfield subtitle,text,text_textfield' => 'Create two new fields.'
),
);
$items['field-update'] = array(
'description' => 'Return URL for field editing web page.',
'core' => array('7+'),
'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.',
'core' => array('7+'),
'arguments' => array(
'type' => 'Recognized values: fields, types. If omitted, a choice list appears.',
),
'examples' => array(
'field-info types' => 'Show a table which lists all field types and their available widgets',
),
'outputformat' => array(
'default' => 'table',
'pipe-format' => 'csv',
'field-labels' => array(
'field-name' => 'Field name',
'type' => 'Field type',
'bundle' => 'Field bundle',
'type-name' => 'Type name',
'widget' => 'Default widget',
'widgets' => 'Widgets',
),
'table-metadata' => array(
'process-cell' => '_drush_field_info_process_cell',
),
'output-data-type' => 'format-table',
),
);
return $items;
}
/**
* Command argument complete callback.
*/
function field_field_create_complete() {
if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
$all = array();
$info = field_info_bundles();
foreach ($info as $entity_type => $bundles) {
$all = array_merge($all, array_keys($bundles));
}
return array('values' => array_unique($bundles));
}
}
/**
* Command argument complete callback.
*/
function field_field_update_complete() {
return field_field_complete_field_names();
}
/**
* Command argument complete callback.
*/
function field_field_delete_complete() {
return field_field_complete_field_names();
}
/**
* Command argument complete callback.
*/
function field_field_clone_complete() {
return field_field_complete_field_names();
}
/**
* Command argument complete callback.
*/
function field_field_info_complete() {
return array('values' => array('fields', 'types'));
}
/**
* List field names for completion.
*
* @return
* Array of available site aliases.
*/
function field_field_complete_field_names() {
if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
$info = field_info_fields();
return array('values' => array_keys($info));
}
}
function drush_field_create($bundle) {
$entity_type = drush_get_option('entity_type', 'node');
$args = func_get_args();
array_shift($args);
if (empty($args)) {
// Just one item in this array for now.
$args[] = drush_field_create_wizard();
}
// Iterate over each field spec.
foreach ($args as $string) {
list($name, $type, $widget) = explode(',', $string);
$info = field_info_field($name);
if (empty($info)) {
// Field does not exist already. Create it.
$field = array(
'field_name' => $name,
'type' => $type,
);
drush_op('field_create_field', $field);
}
// Create the instance.
$instance = array(
'field_name' => $name,
'entity_type' => $entity_type,
'bundle' => $bundle,
);
if ($widget) {
$instance['widget'] = array('type' => $widget);
}
drush_op('field_create_instance', $instance);
$urls[] = drush_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[] = drush_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($all_bundles) > 1) {
$options = array_merge(array('all' => dt('All bundles')), array_combine($all_bundles, $all_bundles));
$bundle = drush_choice($options, dt("Choose a particular bundle or 'All bundles'"));
if (!$bundle) {
return drush_user_abort();
}
$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[] = drush_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 (!isset($type)) {
// Don't ask in 'pipe' mode -- just default to 'fields'.
if (drush_get_context('DRUSH_PIPE')) {
$type = 'fields';
}
else {
$type = drush_choice(array_combine(array('types', 'fields'), array('types', 'fields')), dt('Which information do you wish to see?'));
}
}
$result = array();
switch ($type) {
case 'fields':
drush_hide_output_fields(array('type-name', 'widget', 'widgets'));
$info = field_info_fields();
foreach ($info as $field_name => $field) {
$bundle_strs = array();
foreach ($field['bundles'] as $entity_type => $bundles) {
$bundle_strs += $bundles;
}
$result[$field_name] = array(
'field-name' => $field_name,
'type' => $field['type'],
'bundle' => $bundle_strs,
);
}
break;
case 'types':
drush_hide_output_fields(array('field-name', 'type', 'bundle'));
$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);
$result[$type_name] = array(
'type-name' => $type_name,
'widget' => $type['default_widget'],
'widgets' => $widgets,
);
}
break;
default:
return drush_set_error('DRUSH_FIELD_INVALID_SELECTION', dt("Argument for drush field-info must be 'fields' or 'types'"));
}
return $result;
}
/**
* We need to handle the formatting of cells in table-format
* output specially. In 'types' output, the output data is a simple
* associative array of machine names => human-readable names.
* We choose to show the machine names. In 'fields' output, the
* output data is a list of entity types, each of which contains a list
* of bundles. We comma-separate the bundles, and space-separate
* the entities.
*/
function _drush_field_info_process_cell($data, $metadata) {
$first = reset($data);
if (is_array($first)) {
foreach($data as $entity => $bundles) {
$list[] = drush_format($bundles, array(), 'csv');
}
return drush_format($list, array(), 'list');
}
return drush_format(array_keys($data), array(), 'csv');
}
/**
* 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,202 @@
<?php
/**
* Implementation of hook_drush_help().
*
* This function is called whenever a drush user calls
* 'drush help <name-of-your-command>'
*
* @param
* A string with the help section (prepend with 'drush:')
*
* @return
* A string with the help text for your command.
*/
function help_drush_help($section) {
switch ($section) {
case 'drush:help':
return dt("Drush provides an extensive help system that describes both drush commands and topics of general interest. Use `drush help --filter` to present a list of command categories to view, and `drush topic` for a list of topics that go more in-depth on how to use and extend drush.");
}
}
/**
* 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 help_drush_command() {
$items = array();
$items['help'] = array(
'description' => 'Print this help message. See `drush help help` for more options.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'allow-additional-options' => array('helpsingle'),
'options' => array(
'sort' => 'Sort commands in alphabetical order. Drush waits for full bootstrap before printing any commands when this option is used.',
'filter' => array(
'description' => 'Restrict command list to those commands defined in the specified file. Omit value to choose from a list of names.',
'example-value' => 'category',
'value' => 'optional',
),
),
'arguments' => array(
'command' => 'A command name, or command alias.',
),
'examples' => array(
'drush' => 'List all commands.',
'drush --filter=devel_generate' => 'Show only commands defined in devel_generate.drush.inc',
'drush help pm-download' => 'Show help for one command.',
'drush help dl' => 'Show help for one command using an alias.',
'drush help --format=html' => 'Show an HTML page detailing all available commands.',
'drush help --format=json' => 'All available comamnds, in a machine parseable format.',
),
// Use output format system for all formats except the default presentation.
'outputformat' => array(
'default' => 'table',
'field-labels' => array('name' => 'Name', 'description' => 'Description'),
'output-data-type' => 'format-table',
),
'topics' => array('docs-readme'),
);
return $items;
}
/**
* Command argument complete callback.
*
* For now, this can't move to helpsingle since help command is the entry point for both.
*
* @return
* Array of available command names.
*/
function core_help_complete() {
return array('values' => array_keys(drush_get_commands()));
}
/**
* Command callback for help command. This is the default command, when none
* other has been specified.
*/
function drush_core_help($name = '') {
$format = drush_get_option('format', 'table');
if ($name) {
// helpsingle command builds output when a command is specified.
$options = drush_redispatch_get_options();
if ($name != 'help') {
unset($options['help']);
}
$return = drush_invoke_process('@self' ,'helpsingle', func_get_args(), $options);
drush_backend_set_result($return['object']);
return;
}
// For speed, only bootstrap up to DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION.
drush_bootstrap_max();
drush_get_commands(true);
$implemented = drush_get_commands();
ksort($implemented);
$command_categories = drush_commands_categorize($implemented);
if ($format != 'table') {
return $command_categories;
}
else {
$visible = drush_help_visible($command_categories);
// If the user specified --filter w/out a value, then
// present a choice list of help categories.
if (drush_get_option('filter', FALSE) === TRUE) {
$help_categories = array();
foreach ($command_categories as $key => $info) {
$description = $info['title'];
if (array_key_exists('summary', $info)) {
$description .= ": " . $info['summary'];
}
$help_categories[$key] = $description;
}
$result = drush_choice($help_categories, 'Select a help category:');
if (!$result) {
return drush_user_abort();
}
drush_set_option('filter', $result);
}
// Filter out categories that the user does not want to see
$filter_category = drush_get_option('filter');
if (!empty($filter_category) && ($filter_category !== TRUE)) {
if (!array_key_exists($filter_category, $command_categories)) {
return drush_set_error('DRUSH_NO_CATEGORY', dt("The specified command category !filter does not exist.", array('!filter' => $filter_category)));
}
$command_categories = array($filter_category => $command_categories[$filter_category]);
}
// 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('filter')) {
drush_print_help($global_options_help);
}
drush_help_listing_print($command_categories);
drush_backend_set_result($command_categories);
return;
}
}
// Uncategorize the list of commands. Hiddens have been removed and
// filtering performed.
function drush_help_visible($command_categories) {
$all = array();
foreach ($command_categories as $category => $info) {
$all = array_merge($all, $info['commands']);
}
return $all;
}
/**
* Print CLI table listing all commands.
*/
function drush_help_listing_print($command_categories) {
$all_commands = array();
foreach ($command_categories as $key => $info) {
// Get the commands in this category.
$commands = $info['commands'];
// Build rows for drush_print_table().
$rows = array();
foreach($commands as $cmd => $command) {
$name = $command['aliases'] ? $cmd . ' (' . implode(', ', $command['aliases']) . ')': $cmd;
$rows[$cmd] = array('name' => $name, 'description' => $command['description']);
}
drush_print($info['title'] . ": (" . $key . ")");
drush_print_table($rows, FALSE, array('name' => 20));
}
}
/**
* Build a fake command for the purposes of showing examples and options.
*/
function drush_global_options_command($brief = FALSE) {
$global_options_help = array(
'description' => 'Execute a drush command. Run `drush help [command]` to view command-specific help. Run `drush topic` to read even more documentation.',
'sections' => array(
'options' => 'Global options (see `drush topic core-global-options` for the full list)',
),
'options' => drush_get_global_options($brief),
'examples' => array(
'drush dl cck zen' => 'Download CCK module and Zen theme.',
'drush --uri=http://example.com status' => 'Show status command for the example.com multi-site.',
),
'#brief' => TRUE,
);
$global_options_help += drush_command_defaults('global-options', 'global_options', __FILE__);
drush_command_invoke_all_ref('drush_help_alter', $global_options_help);
ksort($global_options_help['options']);
return $global_options_help;
}

View file

@ -0,0 +1,277 @@
<?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 helpsingle_drush_command() {
$items = array();
$items['helpsingle'] = array(
'description' => 'Print help for a single command',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'allow-additional-options' => TRUE,
'hidden' => TRUE,
'arguments' => array(
'command' => 'A command name, or command alias.',
),
'examples' => array(
'drush help pm-download' => 'Show help for one command.',
'drush help dl' => 'Show help for one command using an alias.',
),
'topics' => array('docs-readme'),
);
return $items;
}
/**
* Command callback. Show help for a single command.
*/
function drush_core_helpsingle($commandstring) {
// First check and see if the command can already be found.
$commands = drush_get_commands();
if (!array_key_exists($commandstring, $commands)) {
// If the command cannot be found, then bootstrap so that
// additional commands will be brought in.
// TODO: We need to do a full bootstrap in order to find module service
// commands. We only need to do this for Drupal 8, though; 7 and earlier
// can stop at DRUSH_BOOTSTRAP_DRUPAL_SITE. Perhaps we could use command
// caching to avoid bootstrapping, if we have collected the commands for
// this site once already.
drush_bootstrap_max();
$commands = drush_get_commands();
}
if (array_key_exists($commandstring, $commands)) {
$command = $commands[$commandstring];
annotationcommand_adapter_add_hook_options($command);
drush_print_help($command);
return TRUE;
}
$shell_aliases = drush_get_context('shell-aliases', array());
if (array_key_exists($commandstring, $shell_aliases)) {
$msg = dt("'@alias-name' is a shell alias. Its value is: !name. See `drush topic docs-shell-aliases` and `drush shell-alias` for more information.", array('@alias-name' => $commandstring, '!name' => $shell_aliases[$commandstring]));
drush_log($msg, 'ok');
return TRUE;
}
return drush_set_error('DRUSH_COMMAND_NOT_FOUND', dt('Invalid command !command.', array('!command' => $commandstring)));
}
/**
* Print the help for a single command to the screen.
*
* @param array $command
* A fully loaded $command array.
*/
function drush_print_help($command) {
_drush_help_merge_subcommand_information($command);
if (!$help = drush_command_invoke_all('drush_help', 'drush:'. $command['command'])) {
$help = array($command['description']);
}
if ($command['strict-option-handling']) {
$command['topics'][] = 'docs-strict-options';
}
// Give commandfiles an opportunity to add examples and options to the command.
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
drush_engine_add_help_topics($command);
drush_command_invoke_all_ref('drush_help_alter', $command);
drush_print(wordwrap(implode("\n", $help), drush_get_context('DRUSH_COLUMNS', 80)));
drush_print();
$global_options = drush_get_global_options();
foreach ($command['global-options'] as $global_option) {
$command['options'][$global_option] = $global_options[$global_option];
}
// Sort command options.
uksort($command['options'], '_drush_help_sort_command_options');
// Print command sections help.
foreach ($command['sections'] as $key => $value) {
if (!empty($command[$key])) {
$rows = drush_format_help_section($command, $key);
if ($rows) {
drush_print(dt($value) . ':');
drush_print_table($rows, FALSE, array('label' => 40));
unset($rows);
drush_print();
}
}
}
// Append aliases if any.
if ($command['aliases']) {
drush_print(dt("Aliases: ") . implode(', ', $command['aliases']));
}
}
/**
* Sort command options alphabetically. Engine options at the end.
*/
function _drush_help_sort_command_options($a, $b) {
$engine_a = strpos($a, '=');
$engine_b = strpos($b, '=');
if ($engine_a && !$engine_b) {
return 1;
}
else if (!$engine_a && $engine_b) {
return -1;
}
elseif ($engine_a && $engine_b) {
if (substr($a, 0, $engine_a) == substr($b, 0, $engine_b)) {
return 0;
}
}
return ($a < $b) ? -1 : 1;
}
/**
* Check to see if the specified command contains an 'allow-additional-options'
* record. If it does, find the additional options that are allowed, and
* add in the help text for the options of all of the sub-commands.
*/
function _drush_help_merge_subcommand_information(&$command) {
// 'allow-additional-options' will either be FALSE (default),
// TRUE ("allow anything"), or an array that lists subcommands
// that are or may be called via drush_invoke by this command.
if (is_array($command['allow-additional-options'])) {
$implemented = drush_get_commands();
foreach ($command['allow-additional-options'] as $subcommand_name) {
if (array_key_exists($subcommand_name, $implemented)) {
$command['options'] += $implemented[$subcommand_name]['options'];
$command['sub-options'] = array_merge_recursive($command['sub-options'], $implemented[$subcommand_name]['sub-options']);
if (empty($command['arguments'])) {
$command['arguments'] = $implemented[$subcommand_name]['arguments'];
}
$command['topics'] = array_merge($command['topics'], $implemented[$subcommand_name]['topics']);
}
}
}
}
/**
* Format one named help section from a command record
*
* @param $command
* A command record with help information
* @param $section
* The name of the section to format ('options', 'topic', etc.)
* @returns array
* Formatted rows, suitable for printing via drush_print_table. The returned
* array can be empty.
*/
function drush_format_help_section($command, $section) {
$rows = array();
$formatter = (function_exists('drush_help_section_formatter_' . $section)) ? 'drush_help_section_formatter_' . $section : 'drush_help_section_default_formatter';
foreach ($command[$section] as $name => $help_attributes) {
if (!is_array($help_attributes)) {
$help_attributes = array('description' => $help_attributes);
}
$help_attributes['label'] = $name;
call_user_func_array($formatter, array($command, &$help_attributes));
if (empty($help_attributes['hidden'])) {
$rows[] = array('label' => $help_attributes['label'], 'description' => $help_attributes['description']);
// Process the subsections too, if any
if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) {
$rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter));
}
}
}
return $rows;
}
/**
* Format one named portion of a subsection from a command record.
* Subsections allow related parts of a help record to be grouped
* together. For example, in the 'options' section, sub-options that
* are related to a particular primary option are stored in a 'sub-options'
* section whose name == the name of the primary option.
*
* @param $command
* A command record with help information
* @param $section
* The name of the section to format ('options', 'topic', etc.)
* @param $subsection
* The name of the subsection (e.g. the name of the primary option)
* @param $formatter
* The name of a function to use to format the rows of the subsection
* @param $prefix
* Characters to prefix to the front of the label (for indentation)
* @returns array
* Formatted rows, suitable for printing via drush_print_table.
*/
function _drush_format_help_subsection($command, $section, $subsection, $formatter, $prefix = ' ') {
$rows = array();
foreach ($command['sub-' . $section][$subsection] as $name => $help_attributes) {
if (!is_array($help_attributes)) {
$help_attributes = array('description' => $help_attributes);
}
$help_attributes['label'] = $name;
call_user_func_array($formatter, array($command, &$help_attributes));
if (!array_key_exists('hidden', $help_attributes)) {
$rows[] = array('label' => $prefix . $help_attributes['label'], 'description' => $help_attributes['description']);
// Process the subsections too, if any
if (!empty($command['sub-' . $section]) && array_key_exists($name, $command['sub-' . $section])) {
$rows = array_merge($rows, _drush_format_help_subsection($command, $section, $name, $formatter, $prefix . ' '));
}
}
}
return $rows;
}
/**
* The options section formatter. Adds a "--" in front of each
* item label. Also handles short-form and example-value
* components in the help attributes.
*/
function drush_help_section_formatter_options($command, &$help_attributes) {
if ($help_attributes['label'][0] == '-') {
drush_log(dt("Option '!option' of command !command should instead be declared as '!fixed'", array('!option' => $help_attributes['label'], '!command' => $command['command'], '!fixed' => preg_replace('/^--*/', '', $help_attributes['label']))), 'debug');
}
else {
$help_attributes['label'] = '--' . $help_attributes['label'];
}
if (!empty($help_attributes['required'])) {
$help_attributes['description'] .= " " . dt("Required.");
}
$prefix = '<';
$suffix = '>';
if (array_key_exists('example-value', $help_attributes)) {
if (isset($help_attributes['value']) && $help_attributes['value'] == 'optional') {
$prefix = '[';
$suffix = ']';
}
$help_attributes['label'] .= '=' . $prefix . $help_attributes['example-value'] . $suffix;
if (array_key_exists('short-form', $help_attributes)) {
$help_attributes['short-form'] .= " $prefix" . $help_attributes['example-value'] . $suffix;
}
}
if (array_key_exists('short-form', $help_attributes)) {
$help_attributes['label'] = '-' . $help_attributes['short-form'] . ', ' . $help_attributes['label'];
}
drush_help_section_default_formatter($command, $help_attributes);
}
/**
* The default section formatter. Replaces '[command]' with the
* command name.
*/
function drush_help_section_default_formatter($command, &$help_attributes) {
// '[command]' is a token representing the current command. @see pm_drush_engine_version_control().
$help_attributes['label'] = str_replace('[command]', $command['command'], $help_attributes['label']);
}

View file

@ -0,0 +1,130 @@
<?php
/**
* @file
* Image module's drush integration.
*
* @todo image-build($field_name, $bundle, $style_name)
*/
use Drush\Log\LogLevel;
/**
* 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+'),
'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.',
),
'aliases' => array('if'),
);
$items['image-derive'] = array(
'description' => 'Create an image derivative.',
'core' => array('7+'),
'drupal dependencies' => array('image'),
'arguments' => array(
'style' => 'An image style machine name.',
'source' => 'Path to a source image. Optionally prepend stream wrapper scheme.',
),
'required arguments' => TRUE,
'options' => array(),
'examples' => array(
'drush image-derive thumbnail themes/bartik/logo.png' => 'Save thumbnail sized derivative of logo image.',
),
'aliases' => array('id'),
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function image_drush_help_alter(&$command) {
// Drupal 8+ customizations.
if ($command['command'] == 'image-derive' && drush_drupal_major_version() >= 8) {
unset($command['examples']);
$command['examples']['drush image-derive thumbnail core/themes/bartik/logo.png'] = 'Save thumbnail sized derivative of logo image.';
}
}
/**
* Command argument complete callback.
*
* @return
* Array of available configuration files for editing.
*/
function image_image_flush_complete() {
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
drush_include_engine('drupal', 'image');
return array('values' => array_keys(drush_image_styles()));
}
function drush_image_flush_pre_validate($style_name = NULL) {
drush_include_engine('drupal', 'image');
if (!empty($style_name) && !$style = drush_image_style_load($style_name)) {
return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name)));
}
}
function drush_image_flush($style_name = NULL) {
drush_include_engine('drupal', 'image');
if (drush_get_option('all')) {
$style_name = 'all';
}
if (empty($style_name)) {
$styles = array_keys(drush_image_styles());
$choices = array_combine($styles, $styles);
$choices = array_merge(array('all' => 'all'), $choices);
$style_name = drush_choice($choices, dt("Choose a style to flush."));
if ($style_name === FALSE) {
return drush_user_abort();
}
}
if ($style_name == 'all') {
foreach (drush_image_styles() as $style_name => $style) {
drush_image_flush_single($style_name);
}
drush_log(dt('All image styles flushed'), LogLevel::SUCCESS);
}
else {
drush_image_flush_single($style_name);
}
}
function drush_image_derive_validate($style_name, $source) {
drush_include_engine('drupal', 'image');
if (!$style = drush_image_style_load($style_name)) {
return drush_set_error(dt('Image style !style not recognized.', array('!style' => $style_name)));
}
if (!file_exists($source)) {
return drush_set_error(dt('Source file not found - !file.', array('!file' => $source)));
}
}
/*
* Command callback. Create an image derivative.
*
* @param string $style_name
* The name of an image style.
*
* @param string $source
* The path to a source image, relative to Drupal root.
*/
function drush_image_derive($style_name, $source) {
drush_include_engine('drupal', 'image');
return _drush_image_derive($style_name, $source);
}

View file

@ -0,0 +1,174 @@
<?php
/**
* @file
* Set up local Drush configuration.
*/
use Drush\Log\LogLevel;
/**
* Implementation of hook_drush_command().
*
* @return
* An associative array describing your command(s).
*/
function init_drush_command() {
$items['core-init'] = array(
'description' => 'Enrich the bash startup file with completion and aliases. Copy .drushrc file to ~/.drush',
'aliases' => array('init'),
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'package' => 'core',
'global-options' => array('editor', 'bg'),
'options' => array(
'edit' => 'Open the new config file in an editor.',
'add-path' => "Always add Drush to the \$PATH in the user's .bashrc file, even if it is already in the \$PATH. Use --no-add-path to skip updating .bashrc with the Drush \$PATH. Default is to update .bashrc only if Drush is not already in the \$PATH.",
),
'examples' => array(
'drush core-init --edit' => 'Enrich Bash and open drush config file in editor.',
'drush core-init --edit --bg' => 'Return to shell prompt as soon as the editor window opens.',
),
);
return $items;
}
/**
* Initialize local Drush configuration
*/
function drush_init_core_init() {
$home = drush_server_home();
$drush_config_dir = $home . "/.drush";
$drush_config_file = $drush_config_dir . "/drushrc.php";
$drush_bashrc = $drush_config_dir . "/drush.bashrc";
$drush_prompt = $drush_config_dir . "/drush.prompt.sh";
$drush_complete = $drush_config_dir . "/drush.complete.sh";
$examples_dir = DRUSH_BASE_PATH . "/examples";
$example_configuration = $examples_dir . "/example.drushrc.php";
$example_bashrc = $examples_dir . "/example.bashrc";
$example_prompt = $examples_dir . "/example.prompt.sh";
$example_complete = DRUSH_BASE_PATH . "/drush.complete.sh";
$bashrc_additions = array();
// Create a ~/.drush directory if it does not yet exist
if (!is_dir($drush_config_dir)) {
drush_mkdir($drush_config_dir);
}
// If there is no ~/.drush/drushrc.php, then copy the
// example Drush configuration file here
if (!is_file($drush_config_file)) {
copy($example_configuration, $drush_config_file);
drush_log(dt("Copied example Drush configuration file to !path", array('!path' => $drush_config_file)), LogLevel::OK);
}
// If Drush is not in the $PATH, then figure out which
// path to add so that Drush can be found globally.
$add_path = drush_get_option('add-path', NULL);
if ((!drush_which("drush") || $add_path) && ($add_path !== FALSE)) {
$drush_path = drush_find_path_to_drush($home);
$drush_path = preg_replace("%^" . preg_quote($home) . "/%", '$HOME/', $drush_path);
$bashrc_additions["%$drush_path%"] = "\n# Path to Drush, added by 'drush init'.\nexport PATH=\"\$PATH:$drush_path\"\n\n";
}
// If there is no ~/.drush/drush.bashrc file, then copy
// the example bashrc file there
if (!is_file($drush_bashrc)) {
copy($example_bashrc, $drush_bashrc);
$pattern = basename($drush_bashrc);
$bashrc_additions["%$pattern%"] = "\n# Include Drush bash customizations.". drush_bash_addition($drush_bashrc);
drush_log(dt("Copied example Drush bash configuration file to !path", array('!path' => $drush_bashrc)), LogLevel::OK);
}
// If there is no ~/.drush/drush.complete.sh file, then copy it there
if (!is_file($drush_complete)) {
copy($example_complete, $drush_complete);
$pattern = basename($drush_complete);
$bashrc_additions["%$pattern%"] = "\n# Include Drush completion.\n". drush_bash_addition($drush_complete);
drush_log(dt("Copied Drush completion file to !path", array('!path' => $drush_complete)), LogLevel::OK);
}
// If there is no ~/.drush/drush.prompt.sh file, then copy
// the example prompt.sh file here
if (!is_file($drush_prompt)) {
copy($example_prompt, $drush_prompt);
$pattern = basename($drush_prompt);
$bashrc_additions["%$pattern%"] = "\n# Include Drush prompt customizations.\n". drush_bash_addition($drush_prompt);
drush_log(dt("Copied example Drush prompt file to !path", array('!path' => $drush_prompt)), LogLevel::OK);
}
// Decide whether we want to add our Bash commands to
// ~/.bashrc or ~/.bash_profile
$bashrc = drush_init_find_bashrc($home);
// Modify the user's bashrc file, adding our customizations.
$bashrc_contents = "";
if (file_exists($bashrc)) {
$bashrc_contents = file_get_contents($bashrc);
}
$new_bashrc_contents = $bashrc_contents;
foreach ($bashrc_additions as $pattern => $addition) {
// Only put in the addition if the pattern does not already
// exist in the bashrc file.
if (!preg_match($pattern, $new_bashrc_contents)) {
$new_bashrc_contents = $new_bashrc_contents . $addition;
}
}
if ($new_bashrc_contents != $bashrc_contents) {
if (drush_confirm(dt(implode('', $bashrc_additions) . "Append the above code to !file?", array('!file' => $bashrc)))) {
file_put_contents($bashrc, "\n\n". $new_bashrc_contents);
drush_log(dt("Updated bash configuration file !path", array('!path' => $bashrc)), LogLevel::OK);
drush_log(dt("Start a new shell in order to experience the improvements (e.g. `bash`)."), LogLevel::OK);
if (drush_get_option('edit')) {
$exec = drush_get_editor();
drush_shell_exec_interactive($exec, $drush_config_file, $drush_config_file);
}
}
else {
return drush_user_abort();
}
}
else {
drush_log(dt('No code added to !path', array('!path' => $bashrc)), LogLevel::OK);
}
}
/**
* Determine which .bashrc file is best to use on this platform.
*/
function drush_init_find_bashrc($home) {
return $home . "/.bashrc";
}
/**
* Determine where Drush is located, so that we can add
* that location to the $PATH
*/
function drush_find_path_to_drush($home) {
// First test: is Drush inside a vendor directory?
// Does vendor/bin exist? If so, use that. We do
// not have a good way to locate the 'bin' directory
// if it has been relocated in the composer.json config
// section.
if ($vendor_pos = strpos(DRUSH_BASE_PATH, "/vendor/")) {
$vendor_dir = substr(DRUSH_BASE_PATH, 0, $vendor_pos + 7);
$vendor_bin = $vendor_dir . '/bin';
if (is_dir($vendor_bin)) {
return $vendor_bin;
}
}
// Fallback is to use the directory that Drush is in.
return DRUSH_BASE_PATH;
}
function drush_bash_addition($file) {
return <<<EOD
if [ -f "$file" ] ; then
source $file
fi
EOD;
}

View file

@ -0,0 +1,125 @@
<?php
/**
* @file
* Provides Drush commands related to Interface Translation.
*/
/**
* Implementation of hook_drush_help().
*/
function locale_drush_help($section) {
switch ($section) {
case 'meta:locale:title':
return dt('Interface translation');
case 'meta:locale:summary':
return dt('Interact with the interface translation system.');
}
}
/**
* Implementation of hook_drush_command().
*/
function locale_drush_command() {
$items['locale-check'] = [
'description' => 'Checks for available translation updates.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
];
$items['locale-update'] = [
'description' => 'Updates the available translations.',
'options' => [
'langcodes' => 'A comma-separated list of language codes to update. If omitted, all translations will be updated.'
],
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
];
// @todo Implement proper export and import commands.
return $items;
}
/**
* Checks for available translation updates.
*
* @see \Drupal\locale\Controller\LocaleController::checkTranslation()
*
* @todo This can be simplified once https://www.drupal.org/node/2631584 lands
* in Drupal core.
*/
function drush_locale_check() {
\Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.compare');
// Check translation status of all translatable project in all languages.
// First we clear the cached list of projects. Although not strictly
// necessary, this is helpful in case the project list is out of sync.
locale_translation_flush_projects();
locale_translation_check_projects();
// Execute a batch if required. A batch is only used when remote files
// are checked.
if (batch_get()) {
drush_backend_batch_process();
}
}
/**
* Imports the available translation updates.
*
* @see TranslationStatusForm::buildForm()
* @see TranslationStatusForm::prepareUpdateData()
* @see TranslationStatusForm::submitForm()
*
* @todo This can be simplified once https://www.drupal.org/node/2631584 lands
* in Drupal core.
*/
function drush_locale_update() {
$module_handler = \Drupal::moduleHandler();
$module_handler->loadInclude('locale', 'fetch.inc');
$module_handler->loadInclude('locale', 'bulk.inc');
$langcodes = [];
foreach (locale_translation_get_status() as $project_id => $project) {
foreach ($project as $langcode => $project_info) {
if (!empty($project_info->type)) {
$langcodes[] = $langcode;
}
}
}
if ($passed_langcodes = drush_get_option('langcodes')) {
$langcodes = array_intersect($langcodes, explode(',', $passed_langcodes));
// @todo Not selecting any language code in the user interface results in
// all translations being updated, so we mimick that behavior here.
}
// Deduplicate the list of langcodes since each project may have added the
// same language several times.
$langcodes = array_unique($langcodes);
// @todo Restricting by projects is not possible in the user interface and is
// broken when attempting to do it in a hook_form_alter() implementation so
// we do not allow for it here either.
$projects = [];
// Set the translation import options. This determines if existing
// translations will be overwritten by imported strings.
$options = _locale_translation_default_update_options();
// If the status was updated recently we can immediately start fetching the
// translation updates. If the status is expired we clear it an run a batch to
// update the status and then fetch the translation updates.
$last_checked = \Drupal::state()->get('locale.translation_last_checked');
if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) {
locale_translation_clear_status();
$batch = locale_translation_batch_update_build(array(), $langcodes, $options);
batch_set($batch);
}
else {
// Set a batch to download and import translations.
$batch = locale_translation_batch_fetch_build($projects, $langcodes, $options);
batch_set($batch);
// Set a batch to update configuration as well.
if ($batch = locale_config_batch_update_components($options, $langcodes)) {
batch_set($batch);
}
}
drush_backend_batch_process();
}

View file

@ -0,0 +1,212 @@
<?php
/**
* @file
* Add system notifications as a new drush option.
*/
/**
* @todo there are no hooks fired after a command errors out.
*/
register_shutdown_function('drush_notify_shutdown');
/**
* Implements hook_drush_help_alter().
*/
function notify_drush_help_alter(&$command) {
if ($command['command'] == 'global-options') {
// Do not include these in options in standard help.
if ($command['#brief'] === FALSE) {
$command['options']['notify'] = array(
'description' => 'Use system notifications to signal command completion. If set to a number, commands that finish in fewer seconds will not trigger a notification.',
'example-value' => 60,
'never-propagate' => TRUE,
);
$command['options']['notify-audio'] = array(
'description' => 'Trigger an audio alert to signal command completion. If set to a number, commands that finish in fewer seconds will not trigger a notification.',
'example-value' => 60,
'never-propagate' => TRUE,
);
$command['sub-options']['notify']['notify-cmd'] = array(
'description' => 'Specify the shell command to trigger the notification.',
'never-propagate' => TRUE,
);
$command['sub-options']['notify']['notify-cmd-audio'] = array(
'description' => 'Specify the shell command to trigger the audio notification.',
'never-propagate' => TRUE,
);
}
}
}
/**
* Implements hook_drush_help().
*/
function notify_drush_help($section) {
switch ($section) {
case 'notify:cache-clear':
return dt('Caches have been cleared.');
case 'notify:site-install:error':
return dt('Failed on site installation');
}
}
/**
* Shutdown function to signal on errors.
*/
function drush_notify_shutdown() {
$cmd = drush_get_command();
if (empty($cmd['command'])) {
return;
}
// pm-download handles its own notification.
if ($cmd['command'] != 'pm-download' && drush_notify_allowed($cmd['command'])) {
$msg = dt("Command '!command' completed.", array('!command' => $cmd['command']));
drush_notify_send(drush_notify_command_message($cmd['command'], $msg));
}
if (drush_get_option('notify', FALSE) && drush_get_error()) {
// If the only error is that notify failed, do not try to notify again.
$log = drush_get_error_log();
if (count($log) == 1 && array_key_exists('NOTIFY_COMMAND_NOT_FOUND', $log)) {
return;
}
// Send an alert that the command failed.
if (drush_notify_allowed($cmd['command'])) {
$msg = dt("Command '!command' failed.", array('!command' => $cmd['command']));
drush_notify_send(drush_notify_command_message($cmd['command'] . ':error', $msg));
}
}
}
/**
* Determine the message to send on command completion.
*
* @param string $command
* Name of the Drush command for which we check message overrides.
* @param string $default
* (Default: NULL) Default message to use if there are not notification message overrides.
*
* @return string
* Message to use for notification.
*/
function drush_notify_command_message($command, $default = NULL) {
if ($msg = drush_command_invoke_all('drush_help', 'notify:' . $command)) {
$msg = implode("\n", $msg);
}
else {
$msg = $default ? $default : $msg = $command . ': No news is good news.';
}
return $msg;
}
/**
* Prepares and dispatches notifications to delivery mechanisms.
*
* You may avoid routing a message to secondary messaging mechanisms (e.g. audio),
* by direct use of the delivery functions.
*
* @param string $msg
* Message to send via notification.
*/
function drush_notify_send($msg) {
drush_notify_send_text($msg);
if (drush_get_option('notify-audio', FALSE)) {
drush_notify_send_audio($msg);
}
}
/**
* Send text-based system notification.
*
* This is the automatic, default behavior. It is intended for use with tools
* such as libnotify in Linux and Notification Center on OSX.
*
* @param string $msg
* Message text for delivery.
*
* @return bool
* TRUE on success, FALSE on failure
*/
function drush_notify_send_text($msg) {
$override = drush_get_option('notify-cmd', FALSE);
if (!empty($override)) {
$cmd = $override;
}
else {
switch (PHP_OS) {
case 'Darwin':
$cmd = 'terminal-notifier -message %s -title Drush';
$error_message = dt('terminal-notifier command failed. Please install it from https://github.com/alloy/terminal-notifier.');
break;
case 'Linux':
default:
$icon = drush_normalize_path(DRUSH_BASE_PATH . '/drush_logo-black.png');
$cmd = "notify-send %s -i $icon";
$error_message = dt('notify-send command failed. Please install it as per http://coderstalk.blogspot.com/2010/02/how-to-install-notify-send-in-ubuntu.html.');
break;
}
}
if (!drush_shell_exec($cmd, $msg)) {
return drush_set_error('NOTIFY_COMMAND_NOT_FOUND', $error_message . ' ' . dt('Or you may specify an alternate command to run by specifying --notify-cmd=<my_command>'));
}
return TRUE;
}
/**
* Send an audio-based system notification.
*
* This function is only automatically invoked with the additional use of the
* --notify-audio flag or configuration state.
*
* @param $msg
* Message for audio recital.
*
* @return bool
* TRUE on success, FALSE on failure
*/
function drush_notify_send_audio($msg) {
$override = drush_get_option('notify-cmd-audio', FALSE);
if (!empty($override)) {
$cmd = $override;
}
else {
switch (PHP_OS) {
case 'Darwin':
$cmd = 'say %s';
break;
case 'Linux':
default:
$cmd = drush_get_option('notify-cmd-audio', 'spd-say') . ' %s';
}
}
if (!drush_shell_exec($cmd, $msg)) {
return drush_set_error('NOTIFY_COMMAND_NOT_FOUND', dt('The third party notification utility failed.'));
}
}
/**
* Identify if the given Drush request should trigger a notification.
*
* @param $command
* Name of the command.
*
* @return
* Boolean
*/
function drush_notify_allowed($command) {
$notify = drush_get_option(array('notify', 'notify-audio'), FALSE);
$execution = time() - $_SERVER['REQUEST_TIME'];
return ($notify === TRUE ||
(is_numeric($notify) && $notify > 0 && $execution > $notify));
}

View file

@ -0,0 +1,475 @@
<?php
/**
* @file
* Core drush output formats.
*/
/**
* @return drush_outputformat The selected output format engine
*/
function drush_get_outputformat() {
return drush_get_engine('outputformat');
}
/**
* Dynamically switch to a new output format. Does NOT override
* user-selected output format.
*/
function drush_set_default_outputformat($format, $metadata = array()) {
$command = drush_get_command();
$command['engines']['outputformat']['default'] = $format;
$outputformat = drush_load_command_engine($command, 'outputformat', $metadata);
}
/**
* Given a command name or a command record, return the
* command formatter that is used to process that command's output.
*/
function drush_get_command_format_metadata($command, $metadata = array()) {
$commands = drush_get_commands();
if (!is_array($command) && array_key_exists($command, $commands)) {
$command = $commands[$command];
}
return drush_get_command_engine_config($command, 'outputformat', $metadata);
}
/**
* Implementation of hook_drush_engine_type_info().
*/
function outputformat_drush_engine_type_info() {
$info = array();
$info['outputformat'] = array(
'description' => 'Output formatting options selection and use.',
'topic' => 'docs-output-formats',
'topic-file' => 'docs/output-formats.md',
'combine-help' => TRUE,
'option' => 'format',
'options' => array(
'format' => array(
'description' => 'Select output format.',
'example-value' => 'json',
),
'fields' => array(
'description' => 'Fields to output.',
'example-value' => 'field1,field2',
'value' => 'required',
'list' => TRUE,
),
'list-separator' => array(
'description' => 'Specify how elements in a list should be separated. In lists of lists, this applies to the elements in the inner lists.',
'hidden' => TRUE,
),
'line-separator' => array(
'description' => 'In nested lists of lists, specify how the outer lists ("lines") should be separated.',
'hidden' => TRUE,
),
'field-labels' => array(
'description' => 'Add field labels before first line of data. Default is on; use --no-field-labels to disable.',
'default' => '1',
'key' => 'include-field-labels',
),
),
// Allow output formats to declare their
// "output data type" instead of their
// "required engine capability" for readability.
'config-aliases' => array(
'output-data-type' => 'require-engine-capability',
),
);
return $info;
}
/**
* Implements hook_drush_engine_ENGINE_TYPE().
*
* The output format types supported are represented by
* the 'engine-capabilities' of the output format engine.
* The different capabilities include:
*
* format-single: A simple string.
*
* format-list: An associative array where the key
* is usually the row label, and the value
* is a simple string. Some list formatters
* render the label, and others (like
* "list" and "csv") throw it away.
*
* format-table: An associative array, where the key
* is the row id, and the value is the
* column data. The column data is also
* an associative array where the key
* is the column id and the value is the
* cell data. The cell data should usually
* be a simple string; however, some
* formatters can recursively format their
* cell contents before rendering (e.g. if
* a cell contains a list of items in an array).
*
* These definitions align with the declared 'output-data-type'
* declared in command records. @see drush_parse_command().
*
* Any output format that does not declare any engine capabilities
* is expected to be able to render any php data structure that is
* passed to it.
*/
function outputformat_drush_engine_outputformat() {
$common_topic_example = array(
"a" => array("b" => 2, "c" => 3),
"d" => array("e" => 5, "f" => 6)
);
$engines = array();
$engines['table'] = array(
'description' => 'A formatted, word-wrapped table.',
'engine-capabilities' => array('format-table'),
);
$engines['key-value'] = array(
'description' => 'A formatted list of key-value pairs.',
'engine-capabilities' => array('format-single', 'format-list', 'format-table'),
'hidden' => TRUE,
);
$engines['key-value-list'] = array(
'implemented-by' => 'list',
'list-item-type' => 'key-value',
'description' => 'A list of formatted lists of key-value pairs.',
'list-field-selection-control' => 1,
'engine-capabilities' => array('format-table'),
'hidden' => TRUE,
);
$engines['json'] = array(
'machine-parsable' => TRUE,
'description' => 'Javascript Object Notation.',
'topic-example' => $common_topic_example,
);
$engines['string'] = array(
'machine-parsable' => TRUE,
'description' => 'A simple string.',
'engine-capabilities' => array('format-single'),
);
$engines['message'] = array(
'machine-parsable' => FALSE, // depends on the label....
'hidden' => TRUE,
);
$engines['print-r'] = array(
'machine-parsable' => TRUE,
'description' => 'Output via php print_r function.',
'verbose-only' => TRUE,
'topic-example' => $common_topic_example,
);
$engines['var_export'] = array(
'machine-parsable' => TRUE,
'description' => 'An array in executable php format.',
'topic-example' => $common_topic_example,
);
$engines['yaml'] = array(
'machine-parsable' => TRUE,
'description' => 'Yaml output format.',
'topic-example' => $common_topic_example,
);
$engines['php'] = array(
'machine-parsable' => TRUE,
'description' => 'A serialized php string.',
'verbose-only' => TRUE,
'topic-example' => $common_topic_example,
);
$engines['config'] = array(
'machine-parsable' => TRUE,
'implemented-by' => 'list',
'list-item-type' => 'var_export',
'description' => "A configuration file in executable php format. The variable name is \"config\", and the variable keys are taken from the output data array's keys.",
'metadata' => array(
'variable-name' => 'config',
),
'list-field-selection-control' => -1,
'engine-capabilities' => array('format-list','format-table'),
'verbose-only' => TRUE,
);
$engines['list'] = array(
'machine-parsable' => TRUE,
'list-item-type' => 'string',
'description' => 'A simple list of values.',
// When a table is printed as a list, only the array keys of the rows will print.
'engine-capabilities' => array('format-list', 'format-table'),
'topic-example' => array('a', 'b', 'c'),
);
$engines['nested-csv'] = array(
'machine-parsable' => TRUE,
'implemented-by' => 'list',
'list-separator' => ',',
'list-item-type' => 'csv-or-string',
'hidden' => TRUE,
);
$engines['csv-or-string'] = array(
'machine-parsable' => TRUE,
'hidden' => TRUE,
);
$engines['csv'] = array(
'machine-parsable' => TRUE,
'implemented-by' => 'list',
'list-item-type' => 'nested-csv',
'labeled-list' => TRUE,
'description' => 'A list of values, one per row, each of which is a comma-separated list of values.',
'engine-capabilities' => array('format-table'),
'topic-example' => array(array('a', 12, 'a@one.com'),array('b', 17, 'b@two.com')),
);
$engines['variables'] = array(
'machine-parsable' => TRUE,
'description' => 'A list of php variable assignments.',
'engine-capabilities' => array('format-table'),
'verbose-only' => TRUE,
'list-field-selection-control' => -1,
'topic-example' => $common_topic_example,
);
$engines['labeled-export'] = array(
'machine-parsable' => TRUE,
'description' => 'A list of php exports, labeled with a name.',
'engine-capabilities' => array('format-table'),
'verbose-only' => TRUE,
'implemented-by' => 'list',
'list-item-type' => 'var_export',
'metadata' => array(
'label-template' => '!label: !value',
),
'list-field-selection-control' => -1,
'topic-example' => $common_topic_example,
);
$engines['html'] = array(
'machine-parsable' => FALSE,
'description' => 'An HTML representation',
'engine-capabilities' => array('format-table'),
);
return $engines;
}
/**
* Implements hook_drush_command_alter
*/
function outputformat_drush_command_alter(&$command) {
// In --pipe mode, change the default format to the default pipe format, or
// to json, if no default pipe format is given.
if (drush_get_context('DRUSH_PIPE') && (isset($command['engines']['outputformat']))) {
$default_format = isset($command['engines']['outputformat']['pipe-format']) ? $command['engines']['outputformat']['pipe-format'] : 'json';
$command['engines']['outputformat']['default'] = $default_format;
}
}
/**
* Implements hook_drush_help_alter().
*/
function outputformat_drush_help_alter(&$command) {
if (isset($command['engines']['outputformat'])) {
$outputformat = $command['engines']['outputformat'];
// If the command defines specific field labels,
// then modify the help for --fields to include
// specific information about the available fields.
if (isset($outputformat['field-labels'])) {
$all_fields = array();
$all_fields_description = array();
foreach ($outputformat['field-labels'] as $field => $human_readable) {
$all_fields[] = $field;
if ((strtolower($field) != strtolower($human_readable)) && !array_key_exists(strtolower($human_readable), $outputformat['field-labels'])) {
$all_fields_description[] = $field . dt(" (or '!other')", array('!other' => strtolower($human_readable)));
}
else {
$all_fields_description[] = $field;
}
}
$field_defaults = isset($outputformat['fields-default']) ? $outputformat['fields-default'] : $all_fields;
$command['options']['fields']['example-value'] = implode(', ', $field_defaults);
$command['options']['fields']['description'] .= ' '. dt('All available fields are: !fields.', array('!fields' => implode(', ', $all_fields_description)));
if (isset($outputformat['fields-default'])) {
$command['options']['full']['description'] = dt("Show the full output, with all fields included.");
}
}
else {
// If the command does not define specific field labels,
// then hide the help for --fields unless the command
// uses output format engines that format tables.
if (isset($outputformat['require-engine-capability']) && is_array($outputformat['require-engine-capability'])) {
if (!in_array('format-table', $outputformat['require-engine-capability'])) {
unset($command['options']['fields']);
unset($command['options']['field-labels']);
}
}
// If the command does define output formats, but does not
// define fields, then just hide the help for the --fields option.
else {
$command['options']['fields']['hidden'] = TRUE;
$command['options']['field-labels']['hidden'] = TRUE;
}
}
// If the command defines a default pipe format, then
// add '--pipe Equivalent to --format=<pipe-default>'.
if (isset($outputformat['pipe-format'])) {
if (isset($command['options']['pipe'])) {
$command['options']['pipe'] .= ' ';
}
else {
$command['options']['pipe'] = '';
}
if (isset($outputformat['pipe-metadata']['message-template'])) {
$command['options']['pipe'] .= dt('Displays output in the form "!message"', array('!message' => $outputformat['pipe-metadata']['message-template']));
}
else {
$command['options']['pipe'] .= dt("Equivalent to --format=!default.", array('!default' => $outputformat['pipe-format']));
}
}
}
}
/**
* Implements hook_drush_engine_topic_additional_text().
*/
function outputformat_drush_engine_topic_additional_text($engine, $instance, $config) {
$result = array();
// If the output format engine has a 'topic-example' in
// its configuration, then format the provided array using
// the output formatter, and insert the result of the
// transform into the topic text.
if ($engine == 'outputformat') {
if (array_key_exists('topic-example', $config)) {
$code = $config['topic-example'];
$formatted = drush_format($code, array(), $instance);
$result[] = dt("Code:\n\nreturn !code;\n\nOutput with --format=!instance:\n\n!formatted", array('!code' => var_export($code, TRUE), '!instance' => $instance, '!formatted' => $formatted));
}
}
return $result;
}
/**
* Interface for output format engines.
*/
class drush_outputformat {
function __construct($config) {
$config += array(
'column-widths' => array(),
'field-mappings' => array(),
'engine-info' => array(),
);
$config['engine-info'] += array(
'machine-parsable' => FALSE,
'metadata' => array(),
);
$config += $config['engine-info']['metadata'];
$this->engine_config = $config;
}
function format_error($message) {
return drush_set_error('DRUSH_FORMAT_ERROR', dt("The output data could not be processed by the selected format '!type'. !message", array('!type' => $this->engine, '!message' => $message)));
}
function formatter_type() {
return $this->engine;
}
function is_list() {
return FALSE;
}
function formatter_is_simple_list() {
if (!isset($this->sub_engine)) {
return false;
}
return ($this->formatter_type() == 'list') && ($this->sub_engine->supports_single_only());
}
function data_type($metadata) {
if (isset($metadata['metameta']['require-engine-capability']) && is_array($metadata['metameta']['require-engine-capability'])) {
return $metadata['metameta']['require-engine-capability'][0];
}
if (isset($metadata['require-engine-capability']) && is_array($metadata['require-engine-capability'])) {
return $metadata['require-engine-capability'][0];
}
return 'unspecified';
}
function supported_data_types($metadata = NULL) {
if ($metadata == NULL) {
$metadata = $this->engine_config;
}
if (isset($metadata['metameta']['engine-info']['engine-capabilities'])) {
return $metadata['metameta']['engine-info']['engine-capabilities'];
}
if (isset($metadata['engine-info']['engine-capabilities'])) {
return $metadata['engine-info']['engine-capabilities'];
}
return array();
}
function supports_single_only($metadata = NULL) {
$supported = $this->supported_data_types($metadata);
return (count($supported) == 1) && ($supported[0] == 'format-single');
}
function get_info($key) {
if (array_key_exists($key, $this->engine_config)) {
return $this->engine_config[$key];
}
elseif (isset($this->sub_engine)) {
return $this->sub_engine->get_info($key);
}
return FALSE;
}
/**
* Perform pre-processing and then format() the $input.
*/
function process($input, $metadata = array()) {
$metadata = array_merge_recursive($metadata, $this->engine_config);
if (isset($metadata['private-fields']) && is_array($input)) {
if (!drush_get_option('show-passwords', FALSE)) {
if (!is_array($metadata['private-fields'])) {
$metadata['private-fields'] = array($metadata['private-fields']);
}
foreach ($metadata['private-fields'] as $private) {
drush_unset_recursive($input, $private);
}
}
}
if (isset($metadata[$this->engine . '-metadata'])) {
$engine_specific_metadata = $metadata[$this->engine . '-metadata'];
unset($metadata[$this->engine . '-metadata']);
$metadata = array_merge($metadata, $engine_specific_metadata);
}
if ((drush_get_context('DRUSH_PIPE')) && (isset($metadata['pipe-metadata']))) {
$pipe_specific_metadata = $metadata['pipe-metadata'];
unset($metadata['pipe-metadata']);
$metadata = array_merge($metadata, $pipe_specific_metadata);
}
$machine_parsable = $this->engine_config['engine-info']['machine-parsable'];
$formatter_type = $machine_parsable ? 'parsable' : 'formatted';
if ((!$machine_parsable) && is_bool($input)) {
$input = $input ? 'TRUE' : 'FALSE';
}
// Run $input through any filters that are specified for this formatter.
if (isset($metadata[$formatter_type . '-filter'])) {
$filters = $metadata[$formatter_type . '-filter'];
if (!is_array($filters)) {
$filters = array($filters);
}
foreach ($filters as $filter) {
if (function_exists($filter)) {
$input = $filter($input, $metadata);
}
}
}
if (isset($metadata['field-labels'])) {
foreach (drush_hide_output_fields() as $hidden_field) {
unset($metadata['field-labels'][$hidden_field]);
}
}
return $this->format($input, $metadata);
}
function format($input, $metadata) {
return $input;
}
}
/**
* Specify that certain fields should not appear in the resulting output.
*/
function drush_hide_output_fields($fields_to_hide = array()) {
$already_hidden = drush_get_context('DRUSH_HIDDEN_OUTPUT_FIELDS');
if (!is_array($fields_to_hide)) {
$fields_to_hide = array($fields_to_hide);
}
$result = array_merge($already_hidden, $fields_to_hide);
drush_set_context('DRUSH_HIDDEN_OUTPUT_FIELDS', $result);
return $result;
}

View file

@ -0,0 +1,21 @@
<?php
/**
* Output formatter 'csv-or-string'
*
* @param $data
* The render data may be either a string or an array
* - string: printed as-is, without quotes.
* - array: the value is printed as a csv list.
*
* This is a helper format for handling nested csv lists.
*/
class drush_outputformat_csv_or_string extends drush_outputformat {
function format($data, $metadata) {
// If the data is an array, print it as a comma-separated list
if (is_array($data)) {
return drush_format($data, $metadata, 'csv');
}
return (string)$data;
}
}

View file

@ -0,0 +1,23 @@
<?php
/**
* Output formatter 'html'
*
* @param $data
*
* @param $metadata
* '' -
* '' -
*/
class drush_outputformat_html extends drush_outputformat {
function format($input, $metadata) {
$input = drush_help_visible($input);
$global_options_command = drush_global_options_command(TRUE);
$global_options_rows = drush_format_help_section($global_options_command, 'options');
ob_start();
require_once __DIR__ . '/html.tpl.php';
$return = ob_get_clean();
return $return;
}
}

View file

@ -0,0 +1,29 @@
<html>
<head><title>Drush help</title><style>dt {font-size: 110%; font-weight: bold}</style></head>
<body>
<h3>Global Options (see `drush topic core-global-options` for the full list)</h3>
<table><?php
foreach ($global_options_rows as $key => $row) {
print '<tr>';
foreach ($row as $value) {
print "<td>" . htmlspecialchars($value) . "</td>\n";
}
print "</tr>\n";
} ?>
</table>
<h3>Command list</h3>
<table><?php
foreach ($input as $key => $command) {
print " <tr><td><a href=\"#$key\">$key</a></td><td>" . $command['description'] . "</td></tr>\n";
} ?>
</table>
<h3>Command detail</h3>
<dl><?php
foreach ($input as $key => $command) {
print "\n<a name=\"$key\"></a><dt>$key</dt><dd><pre>\n";
drush_core_helpsingle($key);
print "</pre></dd>\n";
}
?>
</body>
</html>

View file

@ -0,0 +1,26 @@
<?php
/**
* Output formatter 'json'
*
* @param $data
* The $data parameter is converted to Javascript Object Notation
* @param $metadata
* Unused
*
* Code:
*
* return array(
* "a" => array("b" => 2, "c" => 3),
* "d" => array("e" => 5, "f" => 6)
* );
*
* Output with --format=json:
*
* {"a":{"b":2,"c":3},"d":{"e":5,"f":6}}
*/
class drush_outputformat_json extends drush_outputformat {
function format($input, $metadata) {
return drush_json_encode($input);
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* Output formatter 'key_value'
*
* @param $data
* The $data parameter contains an array of key / value pairs which
* are rendered as "key : value" in a formatted word-wrapped table
* with aligned columns. 'value' is expected to always be a simple string;
* if it is not, it is rendered with var_export.
* @param $metadata
* 'label' - If present, creates a section header "[label]" prior to the data
* 'separator' - If present, used instead of ', ' when impoding data values
* 'ini-item' - If present, selects a single item from any data value that is
* an array and uses it instead of imploding all values together.
*
* Code:
*
* return array(
* "b" => "Two B or ! Two B, that is the comparison",
* "c" => "I see that C has gone to Sea"
* );
*
* Output with --format=key-value:
*
* b : Two B or ! Two B,
* that is the
* comparison
* c : I see that C has gone
* to Sea
*
* Code:
*
* return array(
* "a" => array(
* "b" => "Two B or ! Two B, that is the comparison",
* "c" => "I see that C has gone to Sea"
* ),
* "d" => array(
* "e" => "Elephants and electron microscopes",
* "f" => "My margin is too small"
* )
* );
*
* Output with --format=key-value-list:
*
* b : Two B or ! Two B,
* that is the
* comparison
* c : I see that C has gone
* to Sea
*
* e : Elephants and
* electron microscopes
* f : My margin is too
* small
*/
class drush_outputformat_key_value extends drush_outputformat {
function format($input, $metadata) {
if (!is_array($input)) {
if (isset($metadata['label'])) {
$input = array(dt($metadata['label']) => $input);
}
else {
return $this->format_error(dt('No label provided.'));
}
}
$kv_metadata = isset($metadata['table-metadata']) ? $metadata['table-metadata'] : array();
if ((!isset($kv_metadata['key-value-item'])) && (isset($metadata['field-labels']))) {
$input = drush_select_output_fields($input, $metadata['field-labels'], $metadata['field-mappings']);
}
if (isset($metadata['include-field-labels'])) {
$kv_metadata['include-field-labels'] = $metadata['include-field-labels'];
}
$formatted_table = drush_key_value_to_array_table($input, $kv_metadata);
if ($formatted_table === FALSE) {
return FALSE;
}
return drush_format_table($formatted_table, FALSE, array());
}
}

View file

@ -0,0 +1,156 @@
<?php
/**
* Output formatter 'list'
*
* @param $data
* The $data parameter is expected to be an array of key / value pairs.
* Each key / value pair is passed to some other output formatter for
* rendering; the key becomes the label, $metadata['label'], and the
* value becomes the $data for the sub-formatter.
* @param $metadata
* 'matches' - Specifies the exact kind of list to be rendered in an
* array of two elements. $matches[0] is the full name of the
* list format (e.g. 'string-list'), and $matches[1] is the type
* of the sub-formatter (e.g. 'string'). If not specified, 'string'
* is assumed.
*
* Code:
*
* return array('a', 'b', 'c');
*
* Output with --format=list: (list of string):
*
* a
* b
* c
*/
class drush_outputformat_list extends drush_outputformat {
function is_list() {
return TRUE;
}
function validate() {
// Separate the 'list' and 'filter' metadata from everything
// else in $engine_config['engine-info']
$list_metadata = array();
foreach ($this->engine_config as $key => $value) {
if ((substr($key, 0, 4) == 'list') || (substr($key, -6) == 'filter') || (substr($key, 0, 5) == 'field') || ($key == 'options')) {
unset($this->engine_config[$key]);
$list_metadata[$key] = $value;
}
}
foreach ($this->engine_config['engine-info'] as $key => $value) {
if ((substr($key, 0, 4) == 'list') || (substr($key, -6) == 'filter')) {
unset($this->engine_config['engine-info'][$key]);
$list_metadata[$key] = $value;
}
}
$sub_formatter = isset($list_metadata['list-item-type']) ? $list_metadata['list-item-type'] : 'string';
$this->sub_engine = drush_load_engine('outputformat', $sub_formatter, $this->engine_config);
if (!is_object($this->sub_engine)) {
return drush_set_error('DRUSH_INVALID_SUBFORMATTER', dt("The list output formatter could not load its subformatter: !sub", array('!sub' => $sub_formatter)));
}
$engine_info = $this->engine_config['engine-info'];
$this->engine_config = array(
'engine-info' => array(
'machine-parsable' => $this->sub_engine->engine_config['engine-info']['machine-parsable'],
),
'metameta' => $this->sub_engine->engine_config,
) + $list_metadata;
return TRUE;
}
function format($input, $metadata) {
$output = '';
if (is_array($input)) {
// If this list is processing output from a command that produces table
// @todo - need different example below?
// output, but our subformatter only supports 'single' items (e.g. csv),
// then we will convert our data such that the output will be the keys
// of the table rows.
if (($this->data_type($metadata) == 'format-table') && ($this->supports_single_only($metadata)) && !isset($metadata['list-item'])) {
// If the user specified exactly one field with --fields, then
// use it to select the data column to use instead of the array key.
if (isset($metadata['field-labels']) && (count($metadata['field-labels']) == 1)) {
$first_label = key($metadata['field-labels']);
$input = drush_output_get_selected_field($input, $first_label);
}
else {
$input = array_keys($input);
}
}
$first = TRUE;
$field_selection_control = isset($metadata['list-field-selection-control']) ? $metadata['list-field-selection-control'] : 0;
$selected_output_fields = false;
if (empty($metadata['metameta'])) {
$metameta = $metadata;
unset($metameta['list-item']);
unset($metameta['list-item-default-value']);
}
else {
$metameta = $metadata['metameta'];
}
$list_separator_key = 'list-separator';
if ($this->sub_engine->is_list()) {
$list_separator_key = 'line-separator';
if (isset($metadata['list-separator'])) {
$metameta['list-separator'] = $metadata['list-separator'];
}
}
$separator = isset($metadata[$list_separator_key]) && !empty($metadata[$list_separator_key]) ? $metadata[$list_separator_key] : "\n";
// @todo - bug? we iterate over a hard coded, single item array?
foreach (array('field-labels') as $key) {
if (isset($metadata[$key])) {
$metameta[$key] = $metadata[$key];
}
}
// Include field labels, if specified
if (!isset($metadata['list-item']) && isset($metadata['labeled-list']) && is_array($input) && isset($metadata['field-labels'])) {
if (isset($metadata['include-field-labels']) && $metadata['include-field-labels']) {
array_unshift($input, $metadata['field-labels']);
}
}
foreach ($input as $label => $data) {
// If this output formatter is set to print a single item from each
// element, select that item here.
if (isset($metadata['list-item'])) {
$data = isset($data[$metadata['list-item']]) ? $data[$metadata['list-item']] : $metadata['list-item-default-value'];
}
// If this formatter supports the --fields option, then filter and
// order the fields the user wants here. Note that we need to be
// careful about when we call drush_select_output_fields(); sometimes,
// there will be nested formatters of type 'list', and it would not
// do to select the output fields more than once.
// 'list-field-selection-control can be set to a positive number to
// cause output fields to be selected at a later point in the call chain.
elseif (is_array($data) && isset($metadata['field-labels'])) {
if (!$field_selection_control) {
$data = drush_select_output_fields($data, $metadata['field-labels'], $metadata['field-mappings']);
$selected_output_fields = true;
}
}
$metameta['label'] = $label;
if ($selected_output_fields) {
$metameta['list-field-selection-control'] = -1;
}
elseif ($field_selection_control) {
$metameta['list-field-selection-control'] = $field_selection_control - 1;
}
$formatted_item = $this->sub_engine->format($data, $metameta);
if ($formatted_item === FALSE) {
return FALSE;
}
if (!$first) {
$output .= $separator;
}
if (($separator != "\n") && !empty($separator) && (strpos($formatted_item, $separator) !== FALSE)) {
$formatted_item = drush_wrap_with_quotes($formatted_item);
}
$output .= $formatted_item;
$first = FALSE;
}
}
return $output;
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* Output formatter 'message'
*
* @param $data
* The parameters for the render data
* @param $metadata
* 'message-template' - Provides the template string to use with dt().
*
* Code:
*
* return array('a' => 1, 'b' => 2);
*
* Given 'message-template' == 'The first is !a and the second is !b',
* output with --format=message:
*
* The first is 1 and the second is 2
*/
class drush_outputformat_message extends drush_outputformat {
function format($data, $metadata) {
$result = '';
if (isset($metadata['message-template'])) {
foreach ($data as $key => $value) {
$data_for_dt['!' . $key] = $value;
}
$result = dt($metadata['message-template'], $data_for_dt);
}
return $result;
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* Output formatter 'php'
*
* @param $data
* The $data parameter is rendered as a serialized php string
* @param $metadata
*
* Code:
*
*/
class drush_outputformat_php extends drush_outputformat {
function format($input, $metadata) {
return serialize($input);
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* Output formatter 'print-r'
*
* @param $data
* The $data parameter is rendered with the php print_r function
* @param $metadata
* 'label' - If present, prints "label: " prior to the data
*
* Code:
*
* return array(
* "a" => array("b" => 2, "c" => 3),
* "d" => array("e" => 5, "f" => 6)
* );
*
* Output with --format=print-r:
*
* Array
* (
* [a] => Array
* (
* [b] => 2
* [c] => 3
* )
*
* [d] => Array
* (
* [e] => 5
* [f] => 6
* )
* )
*/
class drush_outputformat_print_r extends drush_outputformat {
function format($input, $metadata) {
if (is_string($input)) {
$output = '"' . $input . '"';
}
elseif (is_array($input) || is_object($input)) {
$output = print_r($input, TRUE);
}
else {
$output = $input;
}
if (isset($metadata['label'])) {
$output = $metadata['label'] . ': ' . $output;
}
return $output;
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* Output formatter 'string'
*
* @param $data
* The render data may be either a string or an array
* string - printed as-is, without quotes
* array - the value of the first item in the array is printed as-is
* @param $metadata
* 'label' - If present, prints "label: " prior to the data
*
* Code:
*
* return DRUSH_VERSION;
*
* Output with --format=string:
*
* 6.0-dev
*/
class drush_outputformat_string extends drush_outputformat {
function format($data, $metadata) {
// If the data is an array, print the value of the first item.
if (is_array($data)) {
if (count($data) > 1) {
return $this->format_error("Multiple rows provided where only one is allowed.");
}
if (!empty($data)) {
$data = reset($data);
}
if (is_array($data)) {
return $this->format_error("Array provided where a string is required.");
}
}
return (string)$data;
}
}

View file

@ -0,0 +1,51 @@
<?php
/**
* Output formatter 'table'
*
* @param $data
* The $data parameter is expected to be an array (keys ignored) of
* rows; each row, in turn, is an array of key / value pairs. Every
* row is expected to have the same set of keys. The data is rendered
* as a formatted word-wrapped table with rows of data cells aligned in
* columns.
* @param $metadata
* 'field-labels' - If present, contains an array of key / value pairs
* that map from the keys in the row columns to the label for the
* column header.
* 'column-widths' - If present, contains an array of key / value pairs,
* where the key is the integer column number, and the value is the
* width that column should be formatted to.
*
* Code:
*
* return array(
* "a" => array("b" => 2, "c" => 3),
* "d" => array("b" => 5, "c" => 6)
* );
*
* Output with --format=table:
*
* b c
* 2 3
* 5 6
*/
class drush_outputformat_table extends drush_outputformat {
function format($input, $metadata) {
$field_list = isset($metadata['field-labels']) ? $metadata['field-labels'] : array();
$widths = array();
$col = 0;
foreach($field_list as $key => $label) {
if (isset($metadata['column-widths'][$key])) {
$widths[$col] = $metadata['column-widths'][$key];
}
++$col;
}
$rows = drush_rows_of_key_value_to_array_table($input, $field_list, $metadata);
$field_labels = array_key_exists('include-field-labels', $metadata) && $metadata['include-field-labels'];
if (!$field_labels) {
array_shift($rows);
}
return drush_format_table($rows, $field_labels, $widths);
}
}

View file

@ -0,0 +1,56 @@
The 'table' formatter will convert an associative array into a formatted,
word-wrapped table. Each item in the associative array represents one row
in the table. Each row is similarly composed of associative arrays, with
the key of each item indicating the column, and the value indicating the
contents of the cell. See below for an example source array.
<p>
The command core-requirements is an example of a command that produces output
in a tabular format.
<p>
<i>$ drush core-requirements</i>
<pre>
Title Severity Description
Cron maintenance Error Last run 2 weeks ago
tasks Cron has not run recently. For more
information, see the online handbook entry for
configuring cron jobs. You can run cron
manually.
Drupal Info 7.19
</pre>
(Note: the output above has been shortened for clarity; the actual output
of core-requirements contains additional rows not shown here.)
<p>
It is possible to determine the available fields by consulting <i>drush
help core requirements</i>:
<pre>
--fields=&lt;title, severity, description&gt; Fields to output. All
available fields are:
title, severity, sid,
description, value,
reason, weight.
</pre>
It is possible to control the fields that appear in the table, and their
order, by naming the desired fields in the --fields option. The space
between items is optional, so `--fields=title,sid` is valid.
<p>
Code:
<pre>
return array (
'cron' =>
array (
'title' => 'Cron maintenance tasks',
'severity' => 2,
'value' => 'Last run 2 weeks ago',
'description' => 'Cron has not run recently. For more information, see the online handbook entry for <a href="http://drupal.org/cron">configuring cron jobs</a>. You can <a href="/admin/reports/status/run-cron">run cron manually</a>.',
'severity-label' => 'Error',
),
'drupal' =>
array (
'title' => 'Drupal',
'value' => '7.19',
'severity' => -1,
'weight' => -10,
'severity-label' => 'Info',
),
)
</pre>

View file

@ -0,0 +1,62 @@
<?php
/**
* Output formatter 'var_export'
*
* Note: this class is also used by format 'config'
*
* @param $data
* The $data parameter is rendered with the php var_export() function
* @param $metadata
* 'label' - If present, prints "$variable['label'] = " prior to the data
* 'variable-name' - If present, provides an alternate name for $variable
* when labels are in use.
*
* Code:
*
* return array(
* "a" => array("b" => 2, "c" => 3),
* "d" => array("e" => 5, "f" => 6)
* );
*
* Output with --format=var_export:
*
* array (
* 'a' =>
* array (
* 'b' => 2,
* 'c' => 3,
* ),
* 'd' =>
* array (
* 'e' => 5,
* 'f' => 6,
* ),
* )
*
* Output with --format=config: (list of export)
*
* $config['a'] = array (
* 'b' => 2,
* 'c' => 3,
* );
* $config['d'] = array (
* 'e' => 5,
* 'f' => 6,
* );
*/
class drush_outputformat_var_export extends drush_outputformat {
function format($input, $metadata) {
if (isset($metadata['label'])) {
$variable_name = isset($metadata['variable-name']) ? $metadata['variable-name'] : 'variables';
$variable_name = preg_replace("/[^a-zA-Z0-9_-]/", "", str_replace(' ', '_', $variable_name));
$label = $metadata['label'];
$label_template = (isset($metadata['label-template'])) ? $metadata['label-template'] : '$!variable["!label"] = !value;';
$output = dt($label_template, array('!variable' => $variable_name, '!label' => $label, '!value' => var_export($input, TRUE)));
}
else {
$output = drush_var_export($input);
}
return $output;
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* Output formatter 'variables'
*
* @param $data
* The $data parameter is expected to be a nested array of key / value pairs.
* The top-level key becomes the variable name, $metadata['variable-name'],
* and the key on the inner-level items becomes the array label,
* $metadata['label']. These items are then rendered by the 'var_export' formatter.
* @param $metadata
* Unused.
*
* Code:
*
* return array(
* "a" => array("b" => 2, "c" => 3),
* "d" => array("e" => 5, "f" => 6)
* );
*
* Output with --format=variables:
*
* $a['b'] = 2;
* $a['c'] = 3;
* $d['e'] = 5;
* $d['f'] = 6;
*/
class drush_outputformat_variables extends drush_outputformat {
function validate() {
$metadata = $this->engine_config;
$this->sub_engine = drush_load_engine('outputformat', 'var_export', $metadata);
if (!is_object($this->sub_engine)) {
return FALSE;
}
return TRUE;
}
function format($data, $metadata) {
$output = '';
if (is_array($data)) {
foreach ($data as $variable_name => $section) {
foreach ($section as $label => $value) {
$metameta = array(
'variable-name' => $variable_name,
'label' => $label,
);
$formatted_item = $this->sub_engine->process($value, $metameta);
if ($formatted_item === FALSE) {
return FALSE;
}
$output .= $formatted_item;
$output .= "\n";
}
}
}
return $output;
}
}

View file

@ -0,0 +1,26 @@
<?php
use Symfony\Component\Yaml\Dumper;
/**
* Output formatter 'yaml'
*
* @param $data
* The $data parameter is rendered in yaml
* @param $metadata
*
* Code:
*
*/
class drush_outputformat_yaml extends drush_outputformat {
function format($input, $metadata) {
$dumper = new Dumper();
// Set Yaml\Dumper's default indentation for nested nodes/collections to
// 2 spaces for consistency with Drupal coding standards.
$dumper->setIndentation(2);
// The level where you switch to inline YAML is set to PHP_INT_MAX to
// ensure this does not occur.
$output = $dumper->dump($input, PHP_INT_MAX, NULL, NULL, TRUE);
return $output;
}
}

View file

@ -0,0 +1,95 @@
<?php
use Drush\Log\LogLevel;
/**
* Implements hook_drush_help().
*/
function queue_drush_help($section) {
switch ($section) {
case 'drush:queue-run':
return dt('Run Drupal queue workers. As opposed to "drush cron" that can only be run one at a time on a single site, "drush queue-run" can be invoked as many times as the server load allows.');
}
}
/**
* Implements hook_drush_command().
*/
function queue_drush_command() {
$items['queue-run'] = array(
'description' => 'Run a specific queue by name',
'arguments' => array(
'queue_name' => 'The name of the queue to run, as defined in either hook_queue_info or hook_cron_queue_info.',
),
'required-arguments' => TRUE,
'options' => array(
'time-limit' => 'The maximum number of seconds allowed to run the queue',
),
);
$items['queue-list'] = array(
'description' => 'Returns a list of all defined queues',
'outputformat' => array(
'default' => 'table',
'pipe-format' => 'csv',
'field-labels' => array(
'queue' => 'Queue',
'items' => 'Items',
'class' => 'Class',
),
'ini-item' => 'items',
'table-metadata' => array(
'key-value-item' => 'items',
),
'output-data-type' => 'format-table',
),
);
return $items;
}
/**
* Validation callback for drush queue-run.
*/
function drush_queue_run_validate($queue_name) {
try {
$queue = drush_queue_get_class();
$queue->getInfo($queue_name);
}
catch (\Drush\Queue\QueueException $exception) {
return drush_set_error('DRUSH_QUEUE_RUN_VALIDATION_ERROR', $exception->getMessage());
}
}
/**
* Return the appropriate queue class.
*/
function drush_queue_get_class() {
return drush_get_class('Drush\Queue\Queue');
}
/**
* Command callback for drush queue-run.
*
* Queue runner that is compatible with queues declared using both
* hook_queue_info() and hook_cron_queue_info().
*
* @param $queue_name
* Arbitrary string. The name of the queue to work with.
*/
function drush_queue_run($queue_name) {
$queue = drush_queue_get_class();
$time_limit = (int) drush_get_option('time-limit');
$start = microtime(TRUE);
$count = $queue->run($queue_name, $time_limit);
$elapsed = microtime(TRUE) - $start;
drush_log(dt('Processed @count items from the @name queue in @elapsed sec.', array('@count' => $count, '@name' => $queue_name, '@elapsed' => round($elapsed, 2))), drush_get_error() ? LogLevel::WARNING : LogLevel::OK);
}
/**
* Command callback for drush queue-list.
*/
function drush_queue_list() {
$queue = drush_queue_get_class();
return $queue->listQueues();
}

View file

@ -0,0 +1,314 @@
<?php
use Drush\Log\LogLevel;
use Drush\Role\RoleBase;
/**
* Implementation of hook_drush_help().
*/
function role_drush_help($section) {
switch ($section) {
case 'meta:role:title':
return dt('Role commands');
case 'meta:role:summary':
return dt('Interact with the role system.');
}
}
/**
* Implements hook_drush_help_alter().
*/
function role_drush_help_alter(&$command) {
// Drupal 8+ has changed role names.
if (in_array($command['command'] , array('role-add-perm', 'role-add-perm', 'role-list')) && drush_drupal_major_version() >= 8) {
foreach ($command['examples'] as $key => $val) {
$newkey = str_replace(array('anonymous user', 'authenticated user'), array('anonymous', 'authenticated'), $key);
$command['examples'][$newkey] = $val;
unset($command['examples'][$key]);
}
}
}
/**
* Implementation of hook_drush_command().
*/
function role_drush_command() {
$items['role-create'] = array(
'description' => 'Create a new role.',
'examples' => array(
"drush role-create 'test role'" => "Create a new role 'test role' on D6 or D7; auto-assign the rid. On D8, 'test role' is the rid, and the human-readable name is set to 'Test role'.",
"drush role-create 'test role' 'Test role'" => "Create a new role with a machine name of 'test role', and a human-readable name of 'Test role'. On D6 and D7, behaves as the previous example."
),
'arguments' => array(
'machine name' => 'The symbolic machine name for the role. Required.',
'human-readable name' => 'A descriptive name for the role. Optional; Drupal 8 only. Ignored in D6 and D7.',
),
'aliases' => array('rcrt'),
);
$items['role-delete'] = array(
'description' => 'Delete a role.',
'examples' => array(
"drush role-delete 'test role'" => "Delete the role 'test role'.",
),
'arguments' => array(
'machine name' => 'The symbolic machine name for the role. Required. In D6 and D7, this may also be a numeric role ID.',
),
'aliases' => array('rdel'),
);
$items['role-add-perm'] = array(
'description' => 'Grant specified permission(s) to a role.',
'examples' => array(
"drush role-add-perm 'anonymous user' 'post comments'" => 'Allow anon users to post comments.',
"drush role-add-perm 'anonymous user' \"'post comments','access content'\"" => 'Allow anon users to post comments and access content.',
"drush role-add-perm 'authenticated user' --module=node" => 'Select a permission from "node" permissions to add to logged in users.'
),
'arguments' => array(
'role' => 'The role to modify. Required.',
'permissions' => 'The list of permission to grant, delimited by commas. Required, unless the --module option is used.',
),
'required-arguments' => 1,
'options' => array(
'module' => 'Select the permission to modify from an interactive list of all permissions available in the specified module.',
),
'global-options' => array(
'cache-clear',
),
'aliases' => array('rap'),
);
$items['role-remove-perm'] = array(
'description' => 'Remove specified permission(s) from a role.',
'examples' => array(
"drush role-remove-perm 'anonymous user' 'access content'" => 'Hide content from anon users.',
),
'arguments' => array(
'role' => 'The role to modify.',
'permissions' => 'The list of permission to grant, delimited by commas. Required, unless the --module option is used.',
),
'required-arguments' => 1,
'options' => array(
'module' => 'Select the permission to modify from an interactive list of all permissions available in the specified module.',
),
'global-options' => array(
'cache-clear',
),
'aliases' => array('rmp'),
);
$items['role-list'] = array(
'description' => 'Display a list of all roles defined on the system. If a role name is provided as an argument, then all of the permissions of that role will be listed. If a permission name is provided as an option, then all of the roles that have been granted that permission will be listed.',
'examples' => array(
"drush role-list --filter='administer nodes'" => 'Display a list of roles that have the administer nodes permission assigned.',
"drush role-list 'anonymous user'" => 'Display all of the permissions assigned to the anon user role.'
),
'arguments' => array(
'role' => 'The role to list. Optional; if specified, lists all permissions assigned to that role. If no role is specified, lists all of the roles available on the system.',
),
'options' => array(
'filter' => 'Limits the list of roles to only those that have been assigned the specified permission. Optional; may not be specified if a role argument is provided.',
),
'outputformat' => array(
'default' => 'table',
'pipe-format' => 'list',
'field-labels' => array('rid' => 'ID', 'label' => 'Role Label', 'perm' => "Permission"),
'output-data-type' => 'format-table',
),
'aliases' => array('rls'),
);
return $items;
}
/**
* Create the specified role
*/
function drush_role_create($rid, $role_name = '') {
$role = drush_role_get_class();
$result = $role->role_create($rid, $role_name);
if ($result !== FALSE) {
drush_log(dt('Created "!role"', array('!role' => $rid)), LogLevel::SUCCESS);
}
return $result;
}
/**
* Create the specified role
*/
function drush_role_delete($rid) {
$role = drush_role_get_class($rid);
if ($role === FALSE) {
return FALSE;
}
$result = $role->delete();
if ($result !== FALSE) {
drush_log(dt('Deleted "!role"', array('!role' => $rid)), LogLevel::SUCCESS);
}
return $result;
}
/**
* Add one or more permission(s) to the specified role.
*
* @param string $rid machine name for a role
* @param null|string $permissions machine names, delimited by commas.
*
* @return bool
*/
function drush_role_add_perm($rid, $permissions = NULL) {
if (is_null($permissions)) {
// Assume --module is used thus inject a FALSE
$perms = array(FALSE);
}
else {
$perms = _convert_csv_to_array($permissions);
}
$added_perm = FALSE;
foreach($perms as $perm) {
$result = drush_role_perm('add', $rid, $perm);
if ($result !== FALSE) {
$added_perm = TRUE;
drush_log(dt('Added "!perm" to "!role"', array('!perm' => $perm, '!role' => $result->name)), LogLevel::SUCCESS);
}
}
if ($added_perm) {
drush_drupal_cache_clear_all();
}
return $result;
}
/**
* Remove permission(s) from the specified role.
*
* @param string $rid machine name for a role
* @param null|string $permissions machine names, delimited by commas.
*
* @return bool
*/
function drush_role_remove_perm($rid, $permissions = NULL) {
if (is_null($permissions)) {
// Assume --module is used thus inject a FALSE
$perms = array(FALSE);
}
else {
$perms = _convert_csv_to_array($permissions);
}
$removed_perm = FALSE;
foreach($perms as $perm) {
$result = drush_role_perm('remove', $rid, $perm);
if ($result !== FALSE) {
$removed_perm = TRUE;
drush_log(dt('Removed "!perm" from "!role"', array('!perm' => $perm, '!role' => $result->name)), LogLevel::OK);
}
}
if ($removed_perm) {
drush_drupal_cache_clear_all();
}
return $result;
}
/**
* Implement permission add / remove operations.
*
* @param string $action 'add' | 'remove'
* @param string $rid
* @param string|null $permission
*
* @return bool|RoleBase
*
* @see drush_set_error()
*/
function drush_role_perm($action, $rid, $permission = NULL) {
$role = drush_role_get_class($rid);
if (!$role) {
return FALSE;
}
// If a permission wasn't provided, but the module option is specified,
// provide a list of permissions provided by that module.
if (!$permission && $module = drush_get_option('module', FALSE)) {
drush_include_engine('drupal', 'environment');
if (!drush_module_exists($module)) {
return drush_set_error('DRUSH_ROLE_ERROR', dt('!module not enabled!', array('!module' => $module)));
}
$module_perms = $role->getModulePerms($module);
if (empty($module_perms)) {
return drush_set_error('DRUSH_ROLE_NO_PERMISSIONS', dt('No permissions found for module !module', array('!module' => $module)));
}
$choice = drush_choice($module_perms, "Enter a number to choose which permission to $action.");
if ($choice === FALSE) {
return drush_user_abort();
}
$permission = $module_perms[$choice];
}
else {
$permissions = $role->getAllModulePerms();
if (!in_array($permission, $permissions)) {
return drush_set_error(dt('Could not find the permission: !perm', array('!perm' => $permission)));
}
}
$result = $role->{$action}($permission);
if ($result === FALSE) {
return FALSE;
}
return $role;
}
/**
* Get core version specific Role handler class.
*
* @param string $role_name
* @return RoleBase
*
* @see drush_get_class().
*/
function drush_role_get_class($role_name = DRUPAL_ANONYMOUS_RID) {
return drush_get_class('Drush\Role\Role', func_get_args());
}
/**
* Displays a list of roles
*/
function drush_role_list($rid = '') {
$result = array();
if (empty($rid)) {
drush_hide_output_fields(array('perm'));
// Get options passed.
$perm = drush_get_option('filter');
$roles = array();
// Get all roles - if $perm is empty user_roles retrieves all roles.
$roles = user_roles(FALSE, $perm);
if (empty($roles)) {
return drush_set_error('DRUSH_NO_ROLES', dt("No roles found."));
}
foreach ($roles as $rid => $value) {
$role = drush_role_get_class($rid);
$result[$role->name] = array(
'rid' => $rid,
'label' => $role->name,
);
}
}
else {
drush_hide_output_fields(array('rid', 'label'));
$role = drush_role_get_class($rid);
if (!$role) {
return FALSE;
}
$perms = $role->getPerms();
foreach ($perms as $permission) {
$result[$permission] = array(
'perm' => $permission);
}
}
return $result;
}

View file

@ -0,0 +1,294 @@
<?php
use Drush\Log\LogLevel;
/*
* Implements COMMAND hook init.
*/
function drush_core_rsync_init() {
// Try to get @self defined when --uri was not provided.
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
}
/**
* A command callback.
*
* @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 source in case it defines aliases used by the destination
_drush_sitealias_preflight_path($source);
// After preflight, evaluate file paths. We evaluate destination paths first, because
// there is a first-one-wins policy with --exclude-paths, and we want --target-command-specific
// to take precedence over --source-command-specific.
$destination_settings = drush_sitealias_evaluate_path($destination, $additional_options, FALSE, "rsync", 'target-');
$source_settings = drush_sitealias_evaluate_path($source, $additional_options, FALSE, "rsync", 'source-');
$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)));
}
// 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 delete files in !target and replace with data from !source", array('!source' => $source_path, '!target' => $destination_path)));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
// Next, check to see if both the source and the destination are remote.
// If so, then we'll process this as an rsync from source to local,
// followed by an rsync from local to the destination.
if (drush_sitealias_is_remote_site($source_settings) && drush_sitealias_is_remote_site($destination_settings)) {
return _drush_core_rsync_both_remote($source, $destination, $additional_options, $source_path);
}
// Exclude settings is the default only when both the source and
// the destination are aliases or site names. Therefore, include
// settings will be the default whenever either the source or the
// destination contains a : or a /.
$include_settings_is_default = (strpos($source . $destination, ':') !== FALSE) || (strpos($source . $destination, '/') !== FALSE);
$options = _drush_build_rsync_options($additional_options, $include_settings_is_default);
// Get all of the args and options that appear after the command name.
$original_args = drush_get_original_cli_args_and_options();
foreach ($original_args as $original_option) {
if ($original_option{0} == '-') {
$options .= ' ' . $original_option;
}
}
drush_backend_set_result($destination_path);
// Go ahead and call rsync with the paths we determined
return drush_core_exec_rsync($source_path, $destination_path, $options);
}
/**
* Make a direct call to rsync after the source and destination paths
* have been evaluated.
*
* @param $source
* Any path that can be passed to rsync.
* @param $destination
* Any path that can be passed to rsync.
* @param $additional_options
* An array of options that overrides whatever was passed in on the command
* line (like the 'process' context, but only for the scope of this one
* call).
* @param $include_settings_is_default
* If TRUE, then settings.php will be transferred as part of the rsync unless
* --exclude-conf is specified. If FALSE, then settings.php will be excluded
* from the transfer unless --include-conf is specified.
* @param $live_output
* If TRUE, output goes directly to the terminal using system(). If FALSE,
* rsync is executed with drush_shell_exec() with output in
* drush_shell_exec_output().
*
* @return
* TRUE on success, FALSE on failure.
*/
function drush_core_call_rsync($source, $destination, $additional_options = array(), $include_settings_is_default = TRUE, $live_output = TRUE) {
$options = _drush_build_rsync_options($additional_options, $include_settings_is_default);
return drush_core_exec_rsync($source, $destination, $options, $additional_options, $live_output);
}
function drush_core_exec_rsync($source, $destination, $options, $additional_options = array(), $live_output = TRUE) {
$ssh_options = drush_get_option_override($additional_options, 'ssh-options', '');
$exec = "rsync -e 'ssh $ssh_options' $options $source $destination";
if ($live_output) {
$exec_result = drush_op_system($exec);
$result = ($exec_result == 0);
}
else {
$result = drush_shell_exec($exec);
}
if (!$result) {
drush_set_error('DRUSH_RSYNC_FAILED', dt("Could not rsync from !source to !dest", array('!source' => $source, '!dest' => $destination)));
}
return $result;
}
function _drush_build_rsync_options($additional_options, $include_settings_is_default = TRUE) {
$options = '';
// Exclude vcs reserved files.
if (!_drush_rsync_option_exists('include-vcs', $additional_options)) {
$vcs_files = drush_version_control_reserved_files();
foreach ($vcs_files as $file) {
$options .= ' --exclude="'.$file.'"';
}
}
else {
unset($additional_options['include-vcs']);
}
$mode = '-akz';
// Process --include-paths and --exclude-paths options the same way
foreach (array('include', 'exclude') as $include_exclude) {
// Get the option --include-paths or --exclude-paths and explode to an array of paths
// that we will translate into an --include or --exclude option to pass to rsync
$inc_ex_path = explode(PATH_SEPARATOR, drush_get_option($include_exclude . '-paths', ''));
foreach ($inc_ex_path as $one_path_to_inc_ex) {
if (!empty($one_path_to_inc_ex)) {
$options .= ' --' . $include_exclude . '="' . $one_path_to_inc_ex . '"';
}
}
// Remove stuff inserted by evaluate path
unset($additional_options[$include_exclude . '-paths']);
unset($additional_options[$include_exclude . '-files-processed']);
}
// drush_core_rsync passes in $include_settings_is_default such that
// 'exclude-conf' is the default when syncing from one alias to
// another, and 'include-conf' is the default when a path component
// is included.
if ($include_settings_is_default ? _drush_rsync_option_exists('exclude-conf', $additional_options) : !_drush_rsync_option_exists('include-conf', $additional_options)) {
$options .= ' --exclude="settings.php"';
unset($additional_options['exclude-conf']);
}
if (_drush_rsync_option_exists('exclude-sites', $additional_options)) {
$options .= ' --include="sites/all" --exclude="sites/*"';
unset($additional_options['exclude-sites']);
}
if (_drush_rsync_option_exists('mode', $additional_options)) {
$mode = "-" . drush_get_option_override($additional_options, 'mode');
unset($additional_options['mode']);
}
if (drush_get_context('DRUSH_VERBOSE')) {
// the drush_op() will be verbose about the command that gets executed.
$mode .= 'v';
$options .= ' --stats --progress';
}
// Check if the user has set $options['rsync-version'] to enable rsync legacy version support.
// Drush was written for rsync 2.6.9 or later, so assume that version if nothing was explicitly set.
$rsync_version = drush_get_option(array('rsync-version','source-rsync-version','target-rsync-version'), '2.6.9');
$options_to_exclude = array('ssh-options');
foreach ($additional_options as $test_option => $value) {
// Downgrade some options for older versions of rsync
if ($test_option == 'remove-source-files') {
if (version_compare($rsync_version, '2.6.4', '<')) {
$test_option = NULL;
drush_log('Rsync does not support --remove-sent-files prior to version 2.6.4; some temporary files may remain undeleted.', LogLevel::WARNING);
}
elseif (version_compare($rsync_version, '2.6.9', '<')) {
$test_option = 'remove-sent-files';
}
}
if ((isset($test_option)) && !in_array($test_option, $options_to_exclude) && (isset($value) && !is_array($value))) {
if (($value === TRUE) || (!isset($value))) {
$options .= " --$test_option";
}
else {
$options .= " --$test_option=" . escapeshellarg($value);
}
}
}
return $mode . $options;
}
function _drush_rsync_option_exists($option, $additional_options) {
if (array_key_exists($option, $additional_options)) {
return TRUE;
}
else {
return drush_get_option($option, FALSE);
}
}
/**
* Handle an rsync operation from a remote site to a remote
* site by first rsync'ing to a local location, and then
* copying that location to its final destination.
*/
function _drush_core_rsync_both_remote($source, $destination, $additional_options, $source_path) {
$options = $additional_options + drush_redispatch_get_options();
// Make a temporary directory to copy to. There are three
// cases to consider:
//
// 1. rsync @src:file.txt @dest:location
// 2. rsync @src:dir @dest:location
// 3. rsync @src:dir/ @dest:location
//
// We will explain each of these in turn.
//
// 1. Copy a single file. We'll split this up like so:
//
// rsync @src:file.txt /tmp/tmpdir
// rsync /tmp/tmpdir/file.txt @dest:location
//
// Since /tmp/tmpdir is empty, we could also rsync from
// '/tmp/tmpdir/' if we wished.
//
// 2. Copy a directory. A directory with the same name
// is copied to the destination. We'll split this up like so:
//
// rsync @src:dir /tmp/tmpdir
// rsync /tmp/tmpdir/dir @dest:location
//
// The result is that everything in 'dir' is copied to @dest,
// and ends up in 'location/dir'.
//
// 3. Copy the contents of a directory. We will split this
// up as follows:
//
// rsync @src:dir/ /tmp/tmpdir
// rsync /tmp/tmpdir/ @dest:location
//
// After the first rsync, everything in 'dir' will end up in
// tmpdir. The second rsync copies everything in tmpdir to
// @dest:location without creating an encapsulating folder
// in the destination (i.e. there is no 'tmpdir' in the destination).
//
// All three of these cases need to be handled correctly in order
// to ensure the correct results. In all cases the first
// rsync always copies to $tmpDir, however the second rsync has
// two cases that depend on the source path. If the source path ends
// in /, the contents of a directory have been copied to $tmpDir, and
// the contents of $tmpDir must be copied to the destination. Otherwise,
// a specific file or directory has been copied to $tmpDir and that
// specific item, identified by basename($source_path) must be copied to
// the destination.
$putInTmpPath = drush_tempdir();
$getFromTmpPath = "$putInTmpPath/";
if (substr($source_path, -1) !== '/') {
$getFromTmpPath .= basename($source_path);
}
// Copy from the source to the temporary location. Exit on failure.
$values = drush_invoke_process('@self', 'core-rsync', array($source, $putInTmpPath), $options);
if ($values['error'] != 0) {
return FALSE;
}
// Copy from the temporary location to the final destination.
$values = drush_invoke_process('@self', 'core-rsync', array($getFromTmpPath, $destination), $options);
return $values['error'] == 0;
}

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,214 @@
<?php
use Drush\Log\LogLevel;
/**
* Implementation of hook_drush_help().
*/
function search_drush_help($section) {
switch ($section) {
case 'meta:search:title':
return dt('Search commands');
case 'meta:search:summary':
return dt('Interact with Drupal\'s core search system.');
}
}
function search_drush_command() {
$items['search-status'] = array(
'description' => 'Show how many items remain to be indexed out of the total.',
'drupal dependencies' => array('search'),
'outputformat' => array(
'default' => 'message',
'pipe-format' => 'message',
'field-labels' => array('remaining' => 'Items not yet indexed', 'total' => 'Total items'),
'message-template' => 'There are !remaining items out of !total still to be indexed.',
'pipe-metadata' => array(
'message-template' => '!remaining/!total',
),
'output-data-type' => 'format-list',
),
);
$items['search-index'] = array(
'description' => 'Index the remaining search items without wiping the index.',
'drupal dependencies' => array('search'),
);
$items['search-reindex'] = array(
'description' => 'Force the search index to be rebuilt.',
'drupal dependencies' => array('search'),
'options' => array(
'immediate' => 'Rebuild the index immediately, instead of waiting for cron.',
),
);
return $items;
}
function drush_search_status() {
list($remaining, $total) = _drush_search_status();
return array(
'remaining' => $remaining,
'total' => $total,
);
}
function _drush_search_status() {
$remaining = 0;
$total = 0;
if (drush_drupal_major_version() >= 8) {
$search_page_repository = \Drupal::service('search.search_page_repository');
foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
$status = $entity->getPlugin()->indexStatus();
$remaining += $status['remaining'];
$total += $status['total'];
}
}
elseif (drush_drupal_major_version() == 7) {
foreach (variable_get('search_active_modules', array('node', 'user')) as $module) {
drush_include_engine('drupal', 'environment');
$status = drush_module_invoke($module, 'search_status');
$remaining += $status['remaining'];
$total += $status['total'];
}
}
else {
drush_include_engine('drupal', 'environment');
foreach (drush_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 = drush_module_invoke($module, 'search', 'status');
if (isset($status['remaining']) && isset($status['total'])) {
$remaining += $status['remaining'];
$total += $status['total'];
}
}
}
return array($remaining, $total);
}
function drush_search_index() {
drush_op('_drush_search_index');
drush_log(dt('The search index has been built.'), LogLevel::OK);
}
function _drush_search_index() {
list($remaining, $total) = _drush_search_status();
register_shutdown_function('search_update_totals');
$failures = 0;
while ($remaining > 0) {
$done = $total - $remaining;
$percent = $done / $total * 100;
drush_log(dt('!percent complete. Remaining items to be indexed: !count', array('!percent' => number_format($percent, 2), '!count' => $remaining)), LogLevel::OK);
$eval = "register_shutdown_function('search_update_totals');";
// Use drush_invoke_process() to start subshell. Avoids out of memory issue.
if (drush_drupal_major_version() >= 8) {
$eval = "drush_module_invoke('search', 'cron');";
}
elseif (drush_drupal_major_version() == 7) {
// If needed, prod drush_module_implements() to recognize our
// hook_node_update_index() implementations.
drush_include_engine('drupal', 'environment');
$implementations = drush_module_implements('node_update_index');
if (!in_array('system', $implementations)) {
// Note that this resets module_implements cache.
drush_module_implements('node_update_index', FALSE, TRUE);
}
foreach (variable_get('search_active_modules', array('node', 'user')) as $module) {
// TODO: Make sure that drush_module_invoke is really available when doing this eval().
$eval .= " drush_module_invoke('$module', 'update_index');";
}
}
else {
// If needed, prod module_implements() to recognize our hook_nodeapi()
// implementations.
$implementations = module_implements('nodeapi');
if (!in_array('system', $implementations)) {
// Note that this resets module_implements cache.
module_implements('nodeapi', FALSE, TRUE);
}
$eval .= " module_invoke_all('update_index');";
}
drush_invoke_process('@self', 'php-eval', array($eval));
$previous_remaining = $remaining;
list($remaining, ) = _drush_search_status();
// Make sure we're actually making progress.
if ($remaining == $previous_remaining) {
$failures++;
if ($failures == 3) {
drush_log(dt('Indexing stalled with @number items remaining.', array(
'@number' => $remaining,
)), LogLevel::ERROR);
return;
}
}
// Only count consecutive failures.
else {
$failures = 0;
}
}
}
function drush_search_reindex() {
drush_print(dt("The search index must be fully rebuilt before any new items can be indexed."));
if (drush_get_option('immediate')) {
drush_print(dt("Rebuilding the index may take a long time."));
}
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
if (drush_drupal_major_version() == 8) {
// D8 CR: https://www.drupal.org/node/2326575
$search_page_repository = \Drupal::service('search.search_page_repository');
foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
$entity->getPlugin()->markForReindex();
}
}
elseif (drush_drupal_major_version() == 7) {
drush_op('search_reindex');
}
else {
drush_op('search_wipe');
}
if (drush_get_option('immediate')) {
drush_op('_drush_search_index');
drush_log(dt('The search index has been rebuilt.'), LogLevel::OK);
}
else {
drush_log(dt('The search index will be rebuilt.'), LogLevel::OK);
}
}
/**
* Fake an implementation of hook_node_update_index() for Drupal 7.
*/
function system_node_update_index($node) {
// Verbose output.
if (drush_get_context('DRUSH_VERBOSE')) {
$nid = $node->nid;
if (is_object($nid)) {
// In D8, this is a FieldItemList.
$nid = $nid->value;
}
drush_log(dt('Indexing node !nid.', array('!nid' => $nid)), LogLevel::OK);
}
}
/**
* Fake an implementation of hook_nodeapi() for Drupal 6.
*/
function system_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if ($op == 'update index') {
// Verbose output.
if (drush_get_context('DRUSH_VERBOSE')) {
drush_log(dt('Indexing node !nid.', array('!nid' => $node->nid)), LogLevel::OK);
}
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Shell alias commands. @see example.drushrc.php for details.
*/
function shellalias_drush_help($section) {
switch ($section) {
case 'drush:shell-alias':
return dt('Print a shell alias record.');
}
}
/**
* Command argument complete callback.
*
* @return
* Array of available site aliases.
*/
function shellalias_shell_alias_complete() {
if ($all = drush_get_context('shell-aliases', array())) {
return array('values' => array_keys($all));
}
}
function shellalias_drush_command() {
$items = array();
$items['shell-alias'] = array(
'description' => 'Print all known shell alias records.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'arguments' => array(
'alias' => 'Shell alias to print',
),
'outputformat' => array(
'default' => 'key-value',
'pipe-format' => 'json',
'simplify-single' => TRUE,
'output-data-type' => 'format-list',
),
'aliases' => array('sha'),
'examples' => array(
'drush shell-alias' => 'List all alias records known to drush.',
'drush shell-alias pull' => 'Print the value of the shell alias \'pull\'.',
),
);
return $items;
}
/**
* Print out the specified shell aliases.
*/
function drush_core_shell_alias($alias = FALSE) {
$shell_aliases = drush_get_context('shell-aliases', array());
if (!$alias) {
return $shell_aliases;
}
elseif (isset($shell_aliases[$alias])) {
return array($alias => $shell_aliases[$alias]);
}
}

View file

@ -0,0 +1,266 @@
<?php
use Drush\Log\LogLevel;
use Drupal\Core\Config\FileStorage;
function site_install_drush_command() {
$items['site-install'] = array(
'description' => 'Install Drupal along with modules/themes/configuration using the specified install profile.',
'arguments' => array(
// In Drupal 7 installation profiles can be marked as exclusive by placing
// a
// @code
// exclusive: true
// @endcode
// line in the profile's info file.
// See https://www.drupal.org/node/1022020 for more information.
//
// In Drupal 8 you can turn your installation profile into a distribution
// by providing a
// @code
// distribution:
// name: 'Distribution name'
// @endcode
// block in the profile's info YAML file.
// See https://www.drupal.org/node/2210443 for more information.
'profile' => 'The install profile you wish to run. Defaults to \'default\' in D6, \'standard\' in D7+, unless an install profile is marked as exclusive (or as a distribution in D8+ terminology) in which case that is used.',
'key=value...' => 'Any additional settings you wish to pass to the profile. Fully supported on D7+, partially supported on D6 (single step configure forms only). The key is in the form [form name].[parameter name] on D7 or just [parameter name] on D6.',
),
'options' => array(
'db-url' => array(
'description' => 'A Drupal 6 style database URL. Only required for initial install - not re-install.',
'example-value' => 'mysql://root:pass@host/db',
),
'db-prefix' => 'An optional table prefix to use for initial install. Can be a key-value array of tables/prefixes in a drushrc file (not the command line).',
'db-su' => array(
'description' => 'Account to use when creating a new database. Must have Grant permission (mysql only). Optional.',
'example-value' => 'root',
),
'db-su-pw' => array(
'description' => 'Password for the "db-su" account. Optional.',
'example-value' => 'pass',
),
'account-name' => 'uid1 name. Defaults to admin',
'account-pass' => 'uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drushrc.php.',
'account-mail' => 'uid1 email. Defaults to admin@example.com',
'locale' => array(
'description' => 'A short language code. Sets the default site language. Language files must already be present. You may use download command to get them.',
'example-value' => 'en-GB',
),
'clean-url'=> 'Defaults to clean; use --no-clean-url to disable. Note that Drupal 8 and later requires clean.',
'site-name' => 'Defaults to Site-Install',
'site-mail' => 'From: for system mailings. Defaults to admin@example.com',
'sites-subdir' => array(
'description' => "Name of directory under 'sites' which should be created. Only needed when the subdirectory does not already exist. Defaults to 'default'",
'value' => 'required',
'example-value' => 'directory_name',
),
'config-dir' => 'A path pointing to a full set of configuration which should be imported after installation.',
),
'examples' => array(
'drush site-install expert --locale=uk' => '(Re)install using the expert install profile. Set default language to Ukrainian.',
'drush site-install --db-url=mysql://root:pass@localhost:port/dbname' => 'Install using the specified DB params.',
'drush site-install --db-url=sqlite://sites/example.com/files/.ht.sqlite' => 'Install using SQLite (D7+ only).',
'drush site-install --account-name=joe --account-pass=mom' => 'Re-install with specified uid1 credentials.',
'drush site-install standard install_configure_form.site_default_country=FR my_profile_form.my_settings.key=value' => 'Pass additional arguments to the profile (D7 example shown here - for D6, omit the form id).',
"drush site-install standard install_configure_form.update_status_module='array(FALSE,FALSE)'" => 'Disable email notification during install and later (D7). If your server has no mail transfer agent, this gets rid of an error during install.',
'drush site-install standard install_configure_form.enable_update_status_module=NULL install_configure_form.enable_update_status_emails=NULL' => 'Disable email notification during install and later (D8). If your server has no mail transfer agent, this gets rid of an error during install.',
),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_ROOT,
'aliases' => array('si'),
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function site_install_drush_help_alter(&$command) {
// Drupal version-specific customizations.
if ($command['command'] == 'site-install') {
if (drush_drupal_major_version() >= 8) {
unset($command['options']['clean-url']);
}
else {
unset($command['options']['config-dir']);
}
}
}
/**
* 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)), LogLevel::WARNING);
drush_set_option('sites-subdir', $lower);
}
// Make sure that we will bootstrap to the 'sites-subdir' site.
drush_set_context('DRUSH_SELECTED_URI', 'http://' . $sites_subdir);
}
if ($config = drush_get_option('config-dir')) {
if (!file_exists($config)) {
return drush_set_error('config_import_target', 'The config source directory does not exist.');
}
if (!is_dir($config)) {
return drush_set_error('config_import_target', 'The config source is not a directory.');
}
$configFiles = glob("$config/*.yml");
if (empty($configFiles)) {
drush_log(dt('Configuration import directory !config does not contain any configuration; will skip import.', array('!config' => $config)), LogLevel::WARNING);
drush_set_option('config-dir', '');
}
}
}
/**
* Perform setup tasks for installation.
*/
function drush_core_pre_site_install($profile = NULL) {
$sql = drush_sql_get_class();
if (!$db_spec = $sql->db_spec()) {
drush_set_error(dt('Could not determine database connection parameters. Pass --db-url option.'));
return;
}
// Make sure URI is set so we get back a proper $alias_record. Needed for quick-drupal.
_drush_bootstrap_selected_uri();
$alias_record = drush_sitealias_get_record('@self');
$sites_subdir = drush_sitealias_local_site_path($alias_record);
// Override with sites-subdir if specified.
if ($dir = drush_get_option('sites-subdir')) {
$sites_subdir = "sites/$dir";
}
$conf_path = $sites_subdir;
// Handle the case where someuse uses --variables to set the file public path. Won't work on D8+.
$files = !empty($GLOBALS['conf']['files_public_path']) ? $GLOBALS['conf']['files_public_path'] : "$conf_path/files";
$settingsfile = "$conf_path/settings.php";
$sitesfile = "sites/sites.php";
$default = realpath($alias_record['root'] . '/sites/default');
$sitesfile_write = drush_drupal_major_version() >= 8 && $conf_path != $default && !file_exists($sitesfile);
if (!file_exists($settingsfile)) {
$msg[] = dt('create a @settingsfile file', array('@settingsfile' => $settingsfile));
}
if ($sitesfile_write) {
$msg[] = dt('create a @sitesfile file', array('@sitesfile' => $sitesfile));
}
if ($sql->db_exists()) {
$msg[] = dt("DROP all tables in your '@db' database.", array('@db' => $db_spec['database']));
}
else {
$msg[] = dt("CREATE the '@db' database.", array('@db' => $db_spec['database']));
}
if (!drush_confirm(dt('You are about to ') . implode(dt(' and '), $msg) . ' Do you want to continue?')) {
return drush_user_abort();
}
// Can't install without sites subdirectory and settings.php.
if (!file_exists($conf_path)) {
if (!drush_mkdir($conf_path) && !drush_get_context('DRUSH_SIMULATE')) {
drush_set_error(dt('Failed to create directory @conf_path', array('@conf_path' => $conf_path)));
return;
}
}
else {
drush_log(dt('Sites directory @subdir already exists - proceeding.', array('@subdir' => $conf_path)));
}
if (!drush_file_not_empty($settingsfile)) {
if (!drush_op('copy', 'sites/default/default.settings.php', $settingsfile) && !drush_get_context('DRUSH_SIMULATE')) {
return drush_set_error(dt('Failed to copy sites/default/default.settings.php to @settingsfile', array('@settingsfile' => $settingsfile)));
}
if (drush_drupal_major_version() == 6) {
// On D6, we have to write $db_url ourselves. In D7+, the installer does it.
file_put_contents($settingsfile, "\n" . '$db_url = \'' . drush_get_option('db-url') . "';\n", FILE_APPEND);
// Instead of parsing and performing string replacement on the configuration file,
// the options are appended and override the defaults.
// Database table prefix
if (!empty($db_spec['db_prefix'])) {
if (is_array($db_spec['db_prefix'])) {
// Write db_prefix configuration as an array
$db_prefix_config = '$db_prefix = ' . var_export($db_spec['db_prefix'], TRUE) . ';';
}
else {
// Write db_prefix configuration as a string
$db_prefix_config = '$db_prefix = \'' . $db_spec['db_prefix'] . '\';';
}
file_put_contents($settingsfile, "\n" . $db_prefix_config . "\n", FILE_APPEND);
}
}
}
// Write an empty sites.php if we are on D8 and using multi-site.
if ($sitesfile_write) {
if (!drush_op('copy', 'sites/example.sites.php', $sitesfile) && !drush_get_context('DRUSH_SIMULATE')) {
return drush_set_error(dt('Failed to copy sites/example.sites.php to @sitesfile', array('@sitesfile' => $sitesfile)));
}
}
// We need to be at least at DRUSH_BOOTSTRAP_DRUPAL_SITE to select the site uri to install to
define('MAINTENANCE_MODE', 'install');
if (drush_drupal_major_version() == 6) {
// The Drupal 6 installer needs to bootstrap up to the specified site.
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
}
else {
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE);
}
if (!$sql->drop_or_create($db_spec)) {
return drush_set_error(dt('Failed to create database: @error', array('@error' => implode(drush_shell_exec_output()))));
}
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, 2);
// Allow for numeric and NULL values to be passed in.
if (is_numeric($value)) {
$value = intval($value);
}
elseif ($value == 'NULL') {
$value = NULL;
}
$form_options[$key] = $value;
}
}
// If the profile is not explicitly set, default to the 'minimal' for an issue-free config import.
if (empty($profile) && drush_get_option('config-dir')) {
$profile = 'minimal';
}
drush_include_engine('drupal', 'site_install');
drush_core_site_install_version($profile, $form_options);
// Post installation, run the configuration import.
if ($config = drush_get_option('config-dir')) {
// Set the destination site UUID to match the source UUID, to bypass a core fail-safe.
$source_storage = new FileStorage($config);
$options = ['yes' => TRUE];
drush_invoke_process('@self', 'config-set', array('system.site', 'uuid', $source_storage->read('system.site')['uuid']), $options);
// Run a full configuration import.
drush_invoke_process('@self', 'config-import', array(), array('source' => $config) + $options);
}
}

View file

@ -0,0 +1,398 @@
<?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_NONE,
'arguments' => array(
'site' => 'Site specification to print',
),
'options' => array(
'with-db' => 'Include the databases structure in the full alias record.',
'with-db-url' => 'Include the short-form db-url in the full alias record.',
'no-db' => 'Do not include the database record in the full alias record (default).',
'with-optional' => 'Include optional default items.',
'alias-name' => 'For a single alias, set the name to use in the output.',
'local-only' => 'Only display sites that are available on the local system (remote-site not set, and Drupal root exists).',
'show-hidden' => 'Include hidden internal elements in site alias output',
),
'outputformat' => array(
'default' => 'config',
'pipe-format' => 'var_export',
'variable-name' => 'aliases',
'hide-empty-fields' => TRUE,
'private-fields' => 'password',
'field-labels' => array('#name' => 'Name', 'root' => 'Root', 'uri' => 'URI', 'remote-host' => 'Host', 'remote-user' => 'User', 'remote-port' => 'Port', 'os' => 'OS', 'ssh-options' => 'SSH options', 'php' => 'PHP'),
'fields-default' => array('#name', 'root', 'uri', 'remote-host', 'remote-user'),
'field-mappings' => array('name' => '#name'),
'output-data-type' => 'format-table',
),
'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\'.',
'drush @none site-alias' => 'Print only actual aliases; omit multisites from the local Drupal installation.',
),
'topics' => array('docs-aliases'),
);
$items['site-set'] = array(
'description' => 'Set a site alias to work on that will persist for the current session.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'handle-remote-commands' => TRUE,
'arguments' => array(
'site' => 'Site specification to use, or "-" for previous site. Omit this argument to "unset"',
),
'aliases' => array('use'),
'examples' => array(
'drush site-set @dev' => 'Set the current session to use the @dev alias.',
'drush site-set user@server/path/to/drupal#sitename' => 'Set the current session to use a remote site via site specification.',
'drush site-set /path/to/drupal#sitename' => 'Set the current session to use a local site via site specification.',
'drush site-set -' => 'Go back to the previously-set site (like `cd -`).',
'drush site-set' => 'Without an argument, any existing site becomes unset.',
),
);
return $items;
}
/**
* Command argument complete callback.
*
* @return
* Array of available site aliases.
*/
function sitealias_site_alias_complete() {
return array('values' => array_keys(_drush_sitealias_all_list()));
}
/**
* Command argument complete callback.
*
* @return
* Array of available site aliases.
*/
function sitealias_site_set_complete() {
return array('values' => array_keys(_drush_sitealias_all_list()));
}
/**
* Return a list of all site aliases known to drush.
*
* The array key is the site alias name, and the array value
* is the site specification for the given alias.
*/
function _drush_sitealias_alias_list() {
return drush_get_context('site-aliases');
}
/**
* Return a list of all of the local sites at the current drupal root.
*
* The array key is the site folder name, and the array value
* is the site specification for that site.
*/
function _drush_sitealias_site_list() {
$site_list = array();
$base_path = drush_get_context('DRUSH_DRUPAL_ROOT');
if ($base_path) {
$base_path .= '/sites';
$files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1);
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 site aliases (remote 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();
// Iterate over the arguments and convert them to alias records
if (!empty($specifications)) {
list($site_list, $not_found) = drush_sitealias_resolve_sitespecs($specifications);
if (!empty($not_found)) {
return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt("Not found: @list", array("@list" => implode(', ', $not_found))));
}
}
// If the user provided no args, then we will return everything.
else {
drush_set_default_outputformat('list');
$site_list = _drush_sitealias_all_list();
// Filter out the hidden items
foreach ($site_list as $site_name => $one_site) {
if (array_key_exists('#hidden', $one_site)) {
unset($site_list[$site_name]);
}
}
}
// Filter for only local sites if specified.
if (drush_get_option('local-only', FALSE)) {
foreach ($site_list as $site_name => $one_site) {
if ( (array_key_exists('remote-site', $one_site)) ||
(!array_key_exists('root', $one_site)) ||
(!is_dir($one_site['root']))
) {
unset($site_list[$site_name]);
}
}
}
return $site_list;
}
/**
* Print out the specified site aliases (or else all) using the format
* specified.
*/
function drush_sitealias_print() {
// Try to get the @self alias to be defined.
$phase = drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
$site_list = _drush_sitealias_user_specified_list();
if ($site_list === FALSE) {
return FALSE;
}
ksort($site_list);
$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) {
$result_record = _drush_sitealias_prepare_record($alias_record);
$site_specs[$site] = $result_record;
}
ksort($site_specs);
return $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_prepare_record($alias_record) {
$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) || 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'])) {
$alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']);
}
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']);
}
// We don't want certain fields to go into the output
if (!drush_get_option('show-hidden')) {
foreach ($alias_record as $key => $value) {
if ($key[0] == '#') {
unset($alias_record[$key]);
}
}
}
// 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']);
}
}
return $alias_record;
}
function _drush_sitealias_print_record($alias_record, $site_alias = '') {
$result_record = _drush_sitealias_prepare_record($alias_record);
// 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($result_record['uri']);
}
}
// 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);
}
$exported_alias = var_export($result_record, TRUE);
drush_print('$aliases[\'' . $alias_name . '\'] = ' . $exported_alias . ';');
}
/**
* Use heuristics to attempt to convert from a site directory to a URI.
* This function should only be used when the URI really is unknown, as
* the mapping is not perfect.
*
* @param site_dir
* A directory, such as domain.com.8080.drupal
*
* @return string
* A uri, such as http://domain.com:8080/drupal
*/
function _drush_sitealias_site_dir_to_uri($site_dir) {
// Protect IP addresses NN.NN.NN.NN by converting them
// temporarily to NN_NN_NN_NN for now.
$uri = preg_replace("/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/", "$1_$2_$3_$4", $site_dir);
// Convert .[0-9]+. into :[0-9]+/
$uri = preg_replace("/\.([0-9]+)\./", ":$1/", $uri);
// Convert .[0-9]$ into :[0-9]
$uri = preg_replace("/\.([0-9]+)$/", ":$1", $uri);
// Convert .(com|net|org|info). into .(com|net|org|info)/
$uri = str_replace(array('.com.', '.net.', '.org.', '.info.'), array('.com/', '.net/', '.org/', '.info/'), $uri);
// If there is a / then convert every . after the / to /
// Then again, if we did this we would break if the path contained a "."
// I hope that the path would never contain a "."...
$pos = strpos($uri, '/');
if ($pos !== false) {
$uri = substr($uri, 0, $pos + 1) . str_replace('.', '/', substr($uri, $pos + 1));
}
// n.b. this heuristic works all the time if there is a port,
// it also works all the time if there is a port and no path,
// but it does not work for domains such as .co.jp with no path,
// and it can fail horribly if someone makes a domain like "info.org".
// Still, I think this is the best we can do short of consulting DNS.
// Convert from NN_NN_NN_NN back to NN.NN.NN.NN
$uri = preg_replace("/([0-9]+)_([0-9]+)_([0-9]+)_([0-9]+)/", "$1.$2.$3.$4", $site_dir);
return 'http://' . $uri;
}
/**
* Validation callback for drush site-set.
*/
function drush_sitealias_site_set_validate() {
if (!function_exists('posix_getppid')) {
$args = array('!command' => 'site-set', '!dependencies' => 'POSIX');
return drush_set_error('DRUSH_COMMAND_PHP_DEPENDENCY_ERROR', dt('Command !command needs the following PHP extensions installed/enabled to run: !dependencies.', $args));
}
}
/**
* Set the DRUPAL_SITE variable by writing it out to a temporary file that we
* then source for persistent site switching.
*
* @param site
* A valid site specification.
*/
function drush_sitealias_site_set($site = '@none') {
if ($filename = drush_sitealias_get_envar_filename()) {
$last_site_filename = drush_sitealias_get_envar_filename('drush-drupal-prev-site-');
if ($site == '-') {
if (file_exists($last_site_filename)) {
$site = file_get_contents($last_site_filename);
}
else {
$site = '@none';
}
}
if ($site == '@self') {
$path = drush_cwd();
$site_record = drush_sitealias_lookup_alias_by_path($path, TRUE);
if (isset($site_record['#name'])) {
$site = '@' . $site_record['#name'];
}
else {
$site = '@none';
}
// Using 'site-set @self' is quiet if there is no change.
$current = is_file($filename) ? trim(file_get_contents($filename)) : "@none";
if ($current == $site) {
return;
}
}
if (_drush_sitealias_set_context_by_name($site)) {
if (file_exists($filename)) {
@unlink($last_site_filename);
@rename($filename, $last_site_filename);
}
$success_message = dt("Site set to !site", array('!site' => $site));
if ($site == '@none') {
if (drush_delete_dir($filename)) {
drush_print($success_message);
}
}
elseif (drush_mkdir(dirname($filename), TRUE)) {
if (file_put_contents($filename, $site)) {
drush_print($success_message);
drush_log(dt("Site information stored in !file", array('!file' => $filename)));
}
}
}
else {
return drush_set_error('DRUPAL_SITE_NOT_FOUND', dt("Could not find a site definition for !site.", array('!site' => $site)));
}
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* The drush site-ssh command for connecting to a remote alias' server via
* SSH, either for an interactive session or to run a shell command.
*/
function ssh_drush_command() {
$items['site-ssh'] = array(
'description' => 'Connect to a Drupal site\'s server via SSH for an interactive session or to run a shell command',
'arguments' => array(
'bash' => 'Bash to execute on target. Optional, except when site-alias is a list.',
),
'options' => array(
'cd' => "Directory to change to. Use a full path, TRUE for the site's Drupal root directory, or --no-cd for the ssh default (usually the remote user's home directory). Defaults to the Drupal root.",
) + drush_shell_exec_proc_build_options(),
'handle-remote-commands' => TRUE,
'strict-option-handling' => TRUE,
'examples' => array(
'drush @mysite ssh' => 'Open an interactive shell on @mysite\'s server.',
'drush @prod ssh ls /tmp' => 'Run "ls /tmp" on @prod site. If @prod is a site list, then ls will be executed on each site.',
'drush @prod ssh git pull' => 'Run "git pull" on the Drupal root directory on the @prod site.',
),
'aliases' => array('ssh'),
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'topics' => array('docs-aliases'),
);
return $items;
}
/**
* Command callback.
*/
function drush_ssh_site_ssh($command = NULL) {
// Get all of the args and options that appear after the command name.
$args = drush_get_original_cli_args_and_options();
// n.b. we do not escape the first (0th) arg to allow `drush ssh 'ls /path'`
// to work in addition to the preferred form of `drush ssh ls /path`.
// Supporting the legacy form means that we cannot give the full path to an
// executable if it contains spaces.
for ($x = 1; $x < count($args); $x++) {
$args[$x] = drush_escapeshellarg($args[$x]);
}
$command = implode(' ', $args);
if (!$alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS')) {
return drush_set_error('DRUSH_MISSING_TARGET_ALIAS', 'A site alias is required. The way you call ssh command has changed to `drush @alias ssh`.');
}
$site = drush_sitealias_get_record($alias);
// If we have multiple sites, run ourselves on each one. Set context back when done.
if (isset($site['site-list'])) {
if (empty($command)) {
drush_set_error('DRUSH_SITE_SSH_COMMAND_REQUIRED', dt('A command is required when multiple site aliases are specified.'));
return;
}
foreach ($site['site-list'] as $alias_single) {
drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias_single);
drush_ssh_site_ssh($command);
}
drush_set_context('DRUSH_TARGET_SITE_ALIAS', $alias);
return;
}
if (!drush_sitealias_is_remote_site($alias)) {
// Local sites run their bash without SSH.
$return = drush_invoke_process('@self', 'core-execute', array($command), array('escape' => FALSE));
return $return['object'];
}
// We have a remote site - build ssh command and run.
$interactive = FALSE;
$cd = drush_get_option('cd', TRUE);
if (empty($command)) {
$command = 'bash -l';
$interactive = TRUE;
}
$cmd = drush_shell_proc_build($site, $command, $cd, $interactive);
$status = drush_shell_proc_open($cmd);
if ($status != 0) {
return drush_set_error('DRUSH_SITE_SSH_ERROR', dt('An error @code occurred while running the command `@command`', array('@command' => $cmd, '@code' => $status)));
}
}

View file

@ -0,0 +1,137 @@
<?php
/**
* @file
* Provides State commands.
*/
/**
* Implementation of hook_drush_help().
*/
function state_drush_help($section) {
switch ($section) {
case 'meta:state:title':
return dt('State commands');
case 'meta:state:summary':
return dt('Interact with the State system.');
}
}
/**
* Implementation of hook_drush_command().
*/
function state_drush_command() {
$items['state-get'] = array(
'description' => 'Display a state value.',
'arguments' => array(
'key' => 'The key name.',
),
'required-arguments' => 1,
'examples' => array(
'drush state-get system.cron_last' => 'Displays last cron run timestamp',
),
'outputformat' => array(
'default' => 'json',
'pipe-format' => 'json',
),
'aliases' => array('sget'),
'core' => array('8+'),
);
$items['state-set'] = array(
'description' => 'Set a state value.',
'arguments' => array(
'key' => 'The state key, for example "system.cron_last".',
'value' => 'The value to assign to the state key. Use \'-\' to read from STDIN.',
),
'required arguments' => 2,
'options' => array(
'format' => array(
'description' => 'Deprecated. See input-format option.',
'example-value' => 'boolean',
'value' => 'required',
),
'input-format' => array(
'description' => 'Type for the value. Use "auto" to detect format from value. Other recognized values are string, integer float, or boolean for corresponding primitive type, or json, yaml for complex types.',
'example-value' => 'boolean',
'value' => 'required',
),
// A convenient way to pass a multiline value within a backend request.
'value' => array(
'description' => 'The value to assign to the state key (if any).',
'hidden' => TRUE,
),
),
'examples' => array(
'drush state-set system.cron_last 1406682882 --format=integer' => 'Sets a timestamp for last cron run.',
'php -r "print json_encode(array(\'drupal\', \'simpletest\'));" | drush state-set --format=json foo.name -'=> 'Set a key to a complex value (e.g. array)',
),
'aliases' => array('sset'),
'core' => array('8+'),
);
$items['state-delete'] = array(
'description' => 'Delete a state value.',
'arguments' => array(
'key' => 'The state key, for example "system.cron_last".',
),
'required arguments' => 1,
'examples' => array(
'drush state-del system.cron_last' => 'Delete state entry for system.cron_last.',
),
'aliases' => array('sdel'),
'core' => array('8+'),
);
return $items;
}
/**
* State get command callback.
*
* @state $key
* The state key.
*/
function drush_state_get($key = NULL) {
return \Drupal::state()->get($key);
}
/**
* State set command callback.
*
* @param $key
* The config key.
* @param $value
* The data to save to state.
*/
function drush_state_set($key = NULL, $value = NULL) {
// This hidden option is a convenient way to pass a value without passing a key.
$value = drush_get_option('value', $value);
if (!isset($value)) {
return drush_set_error('DRUSH_STATE_ERROR', dt('No state value specified.'));
}
// Special flag indicating that the value has been passed via STDIN.
if ($value === '-') {
$value = stream_get_contents(STDIN);
}
// If the value is a string (usual case, unless we are called from code),
// then format the input.
if (is_string($value)) {
$value = drush_value_format($value, drush_get_option('format', 'auto'));
}
\Drupal::state()->set($key, $value);
}
/**
* State delete command callback.
*
* @state $key
* The state key.
*/
function drush_state_delete($key = NULL) {
\Drupal::state()->delete($key);
}

View file

@ -0,0 +1,105 @@
<?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_NONE,
'remote-tty' => TRUE,
'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();
$topics = drush_get_topics();
if (isset($topic_name)) {
foreach (drush_get_topics() as $key => $topic) {
if (strstr($key, $topic_name) === FALSE) {
unset($topics[$key]);
}
}
}
if (empty($topics)) {
return drush_set_error('DRUSH_NO_SUCH_TOPIC', dt("No topics on !topic found.", array('!topic' => $topic_name)));
}
if (count($topics) > 1) {
// Show choice list.
foreach ($topics as $key => $topic) {
$choices[$key] = $topic['description'];
}
natcasesort($choices);
if (!$topic_name = drush_choice($choices, dt('Choose a topic'), '!value (!key)', array(5))) {
return drush_user_abort();
}
}
else {
$keys = array_keys($topics);
$topic_name = array_pop($keys);
}
return drush_dispatch($commands[$topic_name]);
}
/**
* A command argument complete callback.
*
* @return
* Available topic keys.
*/
function topic_core_topic_complete() {
return array('values' => array_keys(drush_get_topics()));
}
/**
* Retrieve all defined topics
*/
function drush_get_topics() {
$commands = drush_get_commands();
foreach ($commands as $key => $command) {
if (!empty($command['topic']) && empty($command['is_alias'])) {
$topics[$key] = $command;
}
}
return $topics;
}

View file

@ -0,0 +1,158 @@
<?php
/**
* @file
* Send scrubbed usage data to drush. Omits arguments and option values in order
* to assure that no sensitive data is shared. See http://drupal.org/node/1246738.
*/
use Drush\Log\LogLevel;
/**
* To send usage data, add the following to a .drushrc.php file:
* $options['drush_usage_log'] = TRUE;
* $options['drush_usage_send'] = TRUE;
* $options['drush_usage_size'] = 51200;
*/
function usage_drush_command() {
$disclaimer = 'Usage statistics contain the Drush command name and the Drush option names, but no arguments or option values.';
$items['usage-show'] = array(
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'description' => 'Show Drush usage information that has been logged but not sent. ' . $disclaimer,
'hidden' => TRUE,
'examples' => array(
'drush usage-show' => 'Show cached usage statistics.',
'$options[\'drush_usage_log\'] = TRUE;' => 'Specify in a .drushrc.php file that usage information should be logged locally in a usage statistics file.',
),
'aliases' => array('ushow'),
);
$items['usage-send'] = array(
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'hidden' => TRUE,
'description' => 'Send anonymous Drush usage information to statistics logging site. ' . $disclaimer,
'examples' => array(
'drush usage-send' => 'Immediately send cached usage statistics.',
'$options[\'drush_usage_send\'] = TRUE;' => 'Specify in a .drushrc.php file that usage information should be sent.',
'$options[\'drush_usage_size\'] = 10240;' => 'Specify the frequency (file size) that usage information should be sent.',
),
'aliases' => array('usend'),
);
return $items;
}
/**
* Log and/or send usage data to Mongolab.
*
* An organization can implement own hook_drush_exit() to send data to a
* different endpoint.
*/
function usage_drush_exit() {
// Ignore statistics for simulated commands. (n.b. in simulated mode, _drush_usage_mongolab will print rather than send statistics)
if (!drush_get_context('DRUSH_SIMULATE')) {
$file = _drush_usage_get_file();
if (drush_get_option('drush_usage_log', FALSE)) {
_drush_usage_log(drush_get_command(), $file);
}
if (drush_get_option('drush_usage_send', FALSE)) {
_drush_usage_mongolab($file, drush_get_option('drush_usage_size', 51200));
}
}
}
/**
* Set option to send usage to Mongolab.
*
* See usage_drush_exit() for more information.
*/
function drush_usage_send() {
$file = _drush_usage_get_file(TRUE);
if ($file) {
drush_set_option('drush_usage_send', TRUE);
drush_set_option('drush_usage_size', 0);
drush_print(dt('To automatically send anonymous usage data, add the following to a .drushrc.php file: $options[\'drush_usage_send\'] = TRUE;'));
}
else {
return drush_set_error('DRUSH_NO_USAGE_FILE', dt('No usage file; set $options[\'drush_usage_log\'] = TRUE; in a .drushrc.php file to enable.'));
}
}
/**
* Displays usage file.
*/
function drush_usage_show() {
$file = _drush_usage_get_file(TRUE);
if ($file) {
$json = '[' . file_get_contents($file) . ']';
$usage_data = json_decode($json);
foreach ($usage_data as $item) {
$cmd = $item->cmd;
$options = (array) $item->opt;
array_unshift($options, '');
drush_print($cmd . implode(' --', $options));
}
}
else {
return drush_set_error('DRUSH_NO_USAGE_FILE', dt('No usage file; set $options[\'drush_usage_log\'] = TRUE; in a .drushrc.php file to enable.'));
}
}
/**
* Returns path to usage file.
*/
function _drush_usage_get_file($required = FALSE) {
$file = drush_directory_cache('usage') . '/usage.txt';
if (!file_exists($file) && $required) {
return FALSE;
}
return $file;
}
function _drush_usage_log($command, $file) {
$options = drush_get_command_options_extended($command);
$used = drush_get_merged_options();
$command_specific = array_intersect(array_keys($used), array_keys($options));
$record = array(
'date' => $_SERVER['REQUEST_TIME'],
'cmd' => $command['command'],
'opt' => $command_specific,
'major' => DRUSH_MAJOR_VERSION,
'minor' => DRUSH_MINOR_VERSION,
'os' => php_uname('s'),
'host' => md5(php_uname('n') . get_current_user()),
);
$prequel = (file_exists($file)) ? ",\n" : "";
if (file_put_contents($file, $prequel . json_encode($record), FILE_APPEND)) {
drush_log(dt('Logged command and option names to local cache.'), LogLevel::DEBUG);
}
else {
drush_log(dt('Failed to log command and option names to local cache.'), LogLevel::DEBUG);
}
}
// We only send data periodically to save network traffic and delay. Files
// are sent once they grow over 50KB (configurable).
function _drush_usage_mongolab($file, $min_size_to_send) {
$json = '[' . file_get_contents($file) . ']';
if (filesize($file) > $min_size_to_send) {
$base = 'https://api.mongolab.com/api/1';
$apikey = '4eb95456e4b0bcd285d8135d'; // submitter account.
$database = 'usage';
$collection = 'usage';
$action = "/databases/$database/collections/$collection";
$url = $base . $action . "?apiKey=$apikey";
$header = 'Content-Type: application/json';
if (!drush_shell_exec("wget -q -O - --no-check-certificate --timeout=20 --header=\"$header\" --post-data %s %s", $json, $url)) {
if (!drush_shell_exec("curl -s --connect-timeout 20 --header \"$header\" --data %s %s", $json, $url)) {
drush_log(dt('Drush usage statistics failed to post.'), LogLevel::DEBUG);
return FALSE;
}
}
drush_log(dt('Drush usage statistics successfully posted.'), LogLevel::DEBUG);
// Empty the usage.txt file.
unlink($file);
return TRUE;
}
}

View file

@ -0,0 +1,283 @@
<?php
use Drush\Log\LogLevel;
/**
* 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.',
'core' => array(6,7),
'arguments' => array(
'name' => 'A string to filter the variables by. Variables whose name contains the string will be listed.',
),
'examples' => array(
'drush vget' => 'List all variables and values.',
'drush vget user' => 'List all variables containing the string "user".',
'drush vget site_mail --exact' => 'Show only the value of the variable with the exact key "site_mail".',
'drush vget site_mail --exact --pipe' => 'Show only the variable with the exact key "site_mail" without changing the structure of the output.',
),
'options' => array(
'exact' => "Only get the one variable that exactly matches the specified name. Output will contain only the variable's value.",
),
'outputformat' => array(
'default' => 'yaml',
'pipe-format' => 'config',
'variable-name' => 'variables',
'table-metadata' => array(
'format' => 'var_export',
),
),
'aliases' => array('vget'),
);
$items['variable-set'] = array(
'description' => "Set a variable.",
'core' => array(6,7),
'arguments' => array(
'name' => 'The name of a variable or the first few letters of its name.',
'value' => 'The value to assign to the variable. Use \'-\' to read the object from STDIN.',
),
'required-arguments' => TRUE,
'options' => array(
'yes' => 'Skip confirmation if only one variable name matches.',
'always-set' => array('description' => 'Older synonym for --exact; deprecated.', 'hidden' => TRUE),
'exact' => 'The exact name of the variable to set has been provided; do not prompt for similarly-named variables.',
'format' => array(
'description' => 'Type for the value. Use "auto" to detect format from value. Other recognized values are string, integer float, or boolean for corresponding primitive type, or json, yaml for complex types.',
'example-value' => 'boolean',
),
),
'examples' => array(
'drush vset --yes preprocess_css TRUE' => 'Set the preprocess_css variable to true. Skip confirmation if variable already exists.',
'drush vset --exact maintenance_mode 1' => 'Take the site offline; skips confirmation even if maintenance_mode variable does not exist. Variable is rewritten to site_offline for Drupal 6.',
'drush vset pr TRUE' => 'Choose from a list of variables beginning with "pr" to set to (bool)true.',
'php -r "print json_encode(array(\'drupal\', \'simpletest\'));" | drush vset --format=json project_dependency_excluded_dependencies -'=> 'Set a variable to a complex value (e.g. array)',
),
'aliases' => array('vset'),
);
$items['variable-delete'] = array(
'core' => array(6,7),
'description' => "Delete a variable.",
'arguments' => array(
'name' => 'The name of a variable or the first few letters of its name.',
),
'required-arguments' => TRUE,
'options' => array(
'yes' => 'Skip confirmation if only one variable name matches.',
'exact' => 'Only delete the one variable that exactly matches the specified name.',
),
'examples' => array(
'drush vdel user_pictures' => 'Delete the user_pictures variable.',
'drush vdel u' => 'Choose from a list of variables beginning with "u" to delete.',
'drush vdel -y --exact maintenance_mode' => 'Bring the site back online, skipping confirmation. Variable is rewritten to site_offline for Drupal 6.',
),
'aliases' => array('vdel'),
);
return $items;
}
/**
* Command argument complete callback.
*/
function variable_variable_get_complete() {
return variable_complete_variables();
}
/**
* Command argument complete callback.
*/
function variable_variable_set_complete() {
return variable_complete_variables();
}
/**
* Command argument complete callback.
*/
function variable_variable_delete_complete() {
return variable_complete_variables();
}
/**
* List variables for completion.
*
* @return
* Array of available variables.
*/
function variable_complete_variables() {
if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
global $conf;
return array('values' => array_keys($conf));
}
}
/**
* Command callback.
* List your site's variables.
*/
function drush_variable_get() {
global $conf;
$exact = drush_get_option('exact', FALSE);
$keys = array_keys($conf);
if ($args = func_get_args()) {
$args[0] = drush_variable_name_adjust($args[0]);
if ($exact) {
$keys = in_array($args[0], $keys) ? array($args[0]) : array();
}
$keys = preg_grep("/{$args[0]}/", $keys);
}
// In --exact mode, if --pipe is not set, then simplify the return type.
if ($exact && !drush_get_context('DRUSH_PIPE')) {
$key = reset($keys);
$returns = isset($conf[$key]) ? $conf[$key] : FALSE;
}
else {
foreach ($keys as $name) {
$value = $conf[$name];
$returns[$name] = $value;
}
}
if (empty($keys)) {
return drush_set_error('No matching variable found.');
}
else {
return $returns;
}
}
/**
* Command callback.
* Set a variable.
*/
function drush_variable_set() {
$args = func_get_args();
$value = $args[1];
if (!isset($value)) {
return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.'));
}
$args[0] = drush_variable_name_adjust($args[0]);
$result = drush_variable_like($args[0]);
$options[] = "$args[0] ". dt('(new variable)');
$match = FALSE;
while (!$match && $name = drush_db_result($result)) {
if ($name == $args[0]) {
$options[0] = $name;
$match = TRUE;
}
else {
$options[] = $name;
}
}
if ($value == '-') {
$value = stream_get_contents(STDIN);
}
// If the value is a string (usual case, unless we are called from code),
// then format the input
if (is_string($value)) {
$value = drush_value_format($value, drush_get_option('format', 'auto'));
}
// Format the output for display
if (is_array($value)) {
$display = "\n" . var_export($value, TRUE);
}
elseif (is_integer($value)) {
$display = $value;
}
elseif (is_bool($value)) {
$display = $value ? "TRUE" : "FALSE";
}
else {
$display = '"' . $value . '"';
}
// Check 'always-set' for compatibility with older scripts; --exact is preferred.
$always_set = drush_get_option('always-set', FALSE) || drush_get_option('exact', FALSE);
if ($always_set || count($options) == 1 || $match) {
variable_set($args[0], $value);
drush_log(dt('!name was set to !value.', array('!name' => $args[0], '!value' => $display)), LogLevel::SUCCESS);
return '';
}
else {
$choice = drush_choice($options, 'Enter a number to choose which variable to set.');
if ($choice === FALSE) {
return drush_user_abort();
}
$choice = $options[$choice];
$choice = str_replace(' ' . dt('(new variable)'), '', $choice);
drush_op('variable_set', $choice, $value);
drush_log(dt('!name was set to !value', array('!name' => $choice, '!value' => $display)), LogLevel::SUCCESS);
}
}
/**
* Command callback.
* Delete a variable.
*/
function drush_variable_delete() {
$args = func_get_args();
$args[0] = drush_variable_name_adjust($args[0]);
// Look for similar variable names.
$result = drush_variable_like($args[0]);
$options = array();
while ($name = drush_db_result($result)) {
$options[] = $name;
}
if (drush_get_option('exact', FALSE)) {
$options = in_array($args[0], $options) ? array($args[0]) : array();
}
if (count($options) == 0) {
drush_print(dt('!name not found.', array('!name' => $args[0])));
return '';
}
if ((count($options) == 1) && drush_get_context('DRUSH_AFFIRMATIVE')) {
drush_op('variable_del', $args[0]);
drush_log(dt('!name was deleted.', array('!name' => $args[0])), LogLevel::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)), LogLevel::SUCCESS);
}
}
}
// Query for similar variable names.
function drush_variable_like($arg) {
return drush_db_select('variable', 'name', 'name LIKE :keyword', array(':keyword' => $arg . '%'), NULL, NULL, 'name');
}
// Unify similar variable names across different versions of Drupal
function drush_variable_name_adjust($arg) {
if (($arg == 'maintenance_mode') && (drush_drupal_major_version() < 7)) {
$arg = 'site_offline';
}
if (($arg == 'site_offline') && (drush_drupal_major_version() >= 7)) {
$arg = 'maintenance_mode';
}
return $arg;
}

View file

@ -0,0 +1,487 @@
<?php
/**
* @file
* Drush integration for views.
*/
use Drush\Log\LogLevel;
use Drupal\views\Analyzer;
use Drupal\views\Entity\View;
use Drupal\views\Views;
use Drupal\Component\Utility\MapArray;
/**
* Implements hook_drush_help().
*/
function views_drush_help($section) {
switch ($section) {
case 'meta:views:title':
return dt('Views commands');
case 'meta:views:summary':
return dt('Views drush commands.');
}
}
/**
* Implements hook_drush_command().
*/
function views_drush_command() {
$items = array();
$base = array(
'core' => array('8+'),
'drupal dependencies' => array('views'),
);
$items['views-dev'] = array(
'description' => 'Set the Views settings to more developer-oriented values.',
'aliases' => array('vd'),
) + $base;
$items['views-list'] = array(
'description' => 'Get a list of all views in the system.',
'aliases' => array('vl'),
'options' => array(
'name' => array(
'description' => 'A string contained in the view\'s name to filter the results with.',
'example-value' => 'node',
'value' => 'required',
),
'tags' => array(
'description' => 'A comma-separated list of views tags by which to filter the results.',
'example-value' => 'default',
'value' => 'required',
),
'status' => array(
'description' => 'Status of the views by which to filter the results. Choices: enabled, disabled.',
'example-value' => 'enabled',
'value' => 'required',
),
),
'examples' => array(
'drush vl' => 'Show a list of all available views.',
'drush vl --name=blog' => 'Show a list of views which names contain "blog".',
'drush vl --tags=tag1,tag2' => 'Show a list of views tagged with "tag1" or "tag2".',
'drush vl --status=enabled' => 'Show a list of enabled views.',
),
'outputformat' => array(
'default' => 'table',
'pipe-format' => 'list',
'field-default' => array('name', 'label', 'description', 'status', 'tag'),
'field-labels' => array('name' => 'Machine Name', 'label' => 'Name', 'description' => 'Description', 'status' => 'Status', 'tag' => 'Tag'),
'output-data-type' => 'format-table',
),
) + $base;
$items['views-execute'] = array(
'description' => 'Execute a view and get the results.',
'aliases' => array('vex'),
'arguments' => array(
'view' => 'The name of the view to execute.',
'display' => 'The display ID to execute. If none specified, the default display will be used.',
),
'required-arguments' => 1,
'options' => array(
'count' => array(
'description' => 'Display a count of the results instead of each row.',
),
'rendered' => array(
'description' => 'Return the results as rendered HTML output for the display.',
),
'show-admin-links' => array(
'description' => 'Show contextual admin links in the rendered markup.',
),
),
'outputformat' => array(
'default' => 'print-r',
'pipe-format' => 'var_export',
),
'examples' => array(
'drush views-execute my_view' => 'Show the result set of the default display for the my_view view.',
'drush views-execute my_view page_1 --rendered' => 'Show the rendered output of the my_view:page_1 view.',
'drush views-execute my_view page_1 3 --count' => 'Show a count of my_view:page_1 with an agument of 3 being passed.',
),
) + $base;
$items['views-analyze'] = array(
'drupal dependencies' => array('views', 'views_ui'),
'description' => 'Get a list of all Views analyze warnings',
'aliases' => array('va'),
'options' => array(
'format' => array(
'description' => 'Define the output format. Known formats are: json, print_r, and export.',
),
),
) + $base;
$items['views-enable'] = array(
'description' => 'Enable the specified views.',
'arguments' => array(
'views' => 'A space delimited list of view names.',
),
'required-arguments' => 1,
'aliases' => array('ven'),
'examples' => array(
'drush ven frontpage taxonomy_term' => 'Enable the frontpage and taxonomy_term views.',
),
) + $base;
$items['views-disable'] = array(
'description' => 'Disable the specified views.',
'arguments' => array(
'views' => 'A space delimited list of view names.',
),
'required-arguments' => 1,
'aliases' => array('vdis'),
'examples' => array(
'drush vdis frontpage taxonomy_term' => 'Disable the frontpage and taxonomy_term views.',
),
) + $base;
return $items;
}
/**
* Command callback function for views-dev command.
*
* Changes the settings to more developer oriented values.
*/
function drush_views_dev() {
$settings = array(
'ui.show.listing_filters' => TRUE,
'ui.show.master_display' => TRUE,
'ui.show.advanced_column' => TRUE,
'ui.always_live_preview' => FALSE,
'ui.always_live_preview_button' => TRUE,
'ui.show.preview_information' => TRUE,
'ui.show.sql_query.enabled' => TRUE,
'ui.show.sql_query.where' => 'above',
'ui.show.performance_statistics' => TRUE,
'ui.show.additional_queries' => TRUE,
'debug.output' => TRUE,
'debug.region' => 'message',
'ui.show.display_embed' => TRUE,
);
$config = \Drupal::configFactory()->getEditable('views.settings');
foreach ($settings as $setting => $value) {
$config->set($setting, $value);
// Convert boolean values into a string to print.
if (is_bool($value)) {
$value = $value ? 'TRUE' : 'FALSE';
}
// Wrap string values in quotes.
elseif (is_string($value)) {
$value = "\"$value\"";
}
drush_log(dt('!setting set to !value', array('!setting' => $setting, '!value' => $value)));
}
// Save the new config.
$config->save();
drush_log(dt('New views configuration saved.'), LogLevel::SUCCESS);
}
/**
* Callback function for views-list command.
*/
function drush_views_list() {
$disabled_views = array();
$enabled_views = array();
$format = drush_get_option('format', FALSE);
$views = \Drupal::entityManager()->getStorage('view')->loadMultiple();
// Get the --name option.
$name = array_filter(drush_get_option_list('name'));
$with_name = !empty($name) ? TRUE : FALSE;
// Get the --tags option.
$tags = array_filter(drush_get_option_list('tags'));
$with_tags = !empty($tags) ? TRUE : FALSE;
// Get the --status option. Store user input appart to reuse it after.
$status = drush_get_option('status', FALSE);
// Throw an error if it's an invalid status.
if ($status && !in_array($status, array('enabled', 'disabled'))) {
return drush_set_error(dt('Invalid status: @status. Available options are "enabled" or "disabled"', array('@status' => $status)));
}
// Setup a row for each view.
foreach ($views as $view) {
// If options were specified, check that first mismatch push the loop to the
// next view.
if ($with_name && !stristr($view->id(), $name[0])) {
continue;
}
if ($with_tags && !in_array($view->get('tag'), $tags)) {
continue;
}
$status_bool = $status == 'enabled';
if ($status && ($view->status() !== $status_bool)) {
continue;
}
$row = array(
'name' => $view->id(),
'label' => $view->label(),
'description' => $view->get('description'),
'status' => $view->status() ? dt('Enabled') : dt('Disabled'),
'tag' => $view->get('tag'),
);
// Place the row in the appropiate array, so we can have disabled views at
// the bottom.
if ($view->status()) {
$enabled_views[] = $row;
}
else{
$disabled_views[] = $row;
}
}
// Sort alphabeticaly.
asort($disabled_views);
asort($enabled_views);
if (count($enabled_views) || count($disabled_views)) {
$rows = array_merge($enabled_views, $disabled_views);
return $rows;
}
else {
drush_log(dt('No views found.'));
}
}
/**
* Drush views execute command.
*/
function drush_views_execute($view_name, $display_id = NULL) {
$args = func_get_args();
$view_args = array();
// If it's more than 2, we have arguments. A display has to be specified in
// that case.
if (count($args) > 2) {
$view_args = array_slice($args, 2);
}
if (!$view = Views::getView($view_name)) {
return drush_set_error(dt('View: "@view" not found.', array('@view' => $view_name)));
}
// Set the display and execute the view.
$view->setDisplay($display_id);
$view->preExecute($view_args);
$view->execute();
if (drush_get_option('count', FALSE)) {
drush_set_default_outputformat('string');
return count($view->result);
}
elseif (!empty($view->result)) {
if (drush_get_option('rendered', FALSE)) {
drush_set_default_outputformat('string');
// Don't show admin links in markup by default.
$view->hide_admin_links = !drush_get_option('show-admin-links', FALSE);
$output = $view->preview();
return drupal_render($output);
}
else {
return $view->result;
}
}
else {
drush_log(dt('No results returned for this view.') ,LogLevel::WARNING);
return NULL;
}
}
/**
* Drush views analyze command.
*/
function drush_views_analyze() {
$messages = NULL;
$messages_count = 0;
$format = drush_get_option('format', FALSE);
$views = \Drupal::entityManager()->getStorage('view')->loadMultiple();
if (!empty($views)) {
$analyzer = \Drupal::service('views.analyzer');
foreach ($views as $view_name => $view) {
$view = $view->getExecutable();
if ($messages = $analyzer->getMessages($view)) {
if ($format) {
$output = drush_format($messages, $format);
drush_print($output);
return $output;
}
else {
drush_print($view_name);
foreach ($messages as $message) {
$messages_count++;
drush_print($message['type'] .': '. $message['message'], 2);
}
}
}
}
drush_log(dt('A total of @total views were analyzed and @messages problems were found.', array('@total' => count($views), '@messages' => $messages_count)), LogLevel::OK);
return $messages;
}
else {
return drush_set_error(dt('There are no views to analyze'));
}
}
/**
* Drush views enable command.
*/
function drush_views_enable() {
$view_names = func_get_args();
_views_drush_op('enable', $view_names);
}
/**
* Drush views disable command.
*/
function drush_views_disable() {
$view_names = func_get_args();
_views_drush_op('disable', $view_names);
}
/**
* Perform operations on view objects.
*
* @param string $op
* The operation to perform.
* @param array $view_names
* An array of view names to load and perform this operation on.
*/
function _views_drush_op($op = '', array $view_names = array()) {
$op_types = _views_drush_op_types();
if (!in_array($op, array_keys($op_types))) {
return drush_set_error(dt('Invalid op type'));
}
$view_names = array_combine($view_names, $view_names);
if ($views = \Drupal::entityManager()->getStorage('view')->loadMultiple($view_names)) {
foreach ($views as $view) {
$tokens = array('@view' => $view->id(), '@action' => $op_types[$op]['action']);
if ($op_types[$op]['validate']($view)) {
$function = 'views_' . $op . '_view';
drush_op($function, $view);
drush_log(dt('View: @view has been @action', $tokens), LogLevel::SUCCESS);
}
else {
drush_log(dt('View: @view is already @action', $tokens), LogLevel::NOTICE);
}
// Remove this view from the viewnames input list.
unset($view_names[$view->id()]);
}
return $views;
}
else {
drush_set_error(dt('No views have been loaded'));
}
// If we have some unmatched/leftover view names that weren't loaded.
if (!empty($view_names)) {
foreach ($view_names as $viewname) {
drush_log(dt('View: @view could not be found.', array('@view' => $viewname)), LogLevel::ERROR);
}
}
}
/**
* Returns an array of op types that can be performed on views.
*
* @return array
* An associative array keyed by op type => action name.
*/
function _views_drush_op_types() {
return array(
'enable' => array(
'action' => dt('enabled'),
'validate' => '_views_drush_view_is_disabled',
),
'disable' => array(
'action' => dt('disabled'),
'validate' => '_views_drush_view_is_enabled',
),
);
}
/**
* Returns whether a view is enabled.
*
* @param Drupal\views\Entity\ViewDrupal\views\ $view
* The view object to check.
*
* @return bool
* TRUE if the View is enabled, FALSE otherwise.
*/
function _views_drush_view_is_enabled(View $view) {
return $view->status();
}
/**
* Returns whether a view is disabled.
*
* @param Drupal\views\Entity\View $view
* The view object to check.
*
* @return bool
* TRUE if the View is disabled, FALSE otherwise.
*/
function _views_drush_view_is_disabled(View $view) {
return !$view->status();
}
/**
* Implements hook_cache_clear. Adds a cache clear option for views.
*/
function views_drush_cache_clear(&$types, $include_bootstrapped_types) {
if ($include_bootstrapped_types && \Drupal::moduleHandler()->moduleExists('views')) {
$types['views'] = 'views_invalidate_cache';
}
}
/**
* Command argument complete callback.
*/
function views_views_enable_complete() {
return _drush_views_complete();
}
/**
* Command argument complete callback.
*/
function views_views_disable_complete() {
return _drush_views_complete();
}
/**
* Helper function to return a list of view names for complete callbacks.
*
* @return array
* An array of available view names.
*/
function _drush_views_complete() {
drush_bootstrap_max();
return array('values' => array_keys(\Drupal::entityManager()->getStorage('view')->loadMultiple()));
}

View file

@ -0,0 +1,397 @@
<?php
use Drush\Log\LogLevel;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Html;
/**
* Implementation of hook_drush_help().
*/
function watchdog_drush_help($section) {
switch ($section) {
case 'meta:watchdog:title':
return dt('Watchdog commands');
case 'meta:watchdog:summary':
return dt('Interact with Drupal\'s db logging system.');
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' => array('dblog'),
'outputformat' => array(
'default' => 'table',
'pipe-format' => 'var_export',
'field-labels' => array('wid' => 'ID', 'type' => 'Type', 'message' => 'Message', 'severity' => 'Severity', 'location' => 'Location', 'hostname' => 'Hostname', 'date' => 'Date', 'username' => 'Username'),
'fields-default' => array('wid', 'date', 'type', 'severity', 'message'),
'column-widths' => array('type' => 8, 'severity' => 8),
'output-data-type' => 'format-table',
),
'aliases' => array('wd-list'),
);
$items['watchdog-show'] = array(
'description' => 'Show watchdog messages.',
'drupal dependencies' => array('dblog'),
'arguments' => array(
'wid' => 'Optional id of a watchdog message to show in detail. If not provided, a listing of most recent 10 messages will be displayed. Alternatively if a string is provided, watchdog messages will be filtered by it.',
),
'options' => array(
'count' => 'The number of messages to show. Defaults to 10.',
'severity' => 'Restrict to messages of a given severity level.',
'type' => 'Restrict to messages of a given type.',
'tail' => 'Continuously show new watchdog messages until interrupted.',
'sleep-delay' => 'To be used in conjunction with --tail. This is the number of seconds to wait between each poll to the database. Delay is 1 second by default.',
'extended' => 'Return extended information about each message.',
),
'examples' => array(
'drush watchdog-show' => 'Show a listing of most recent 10 messages.',
'drush watchdog-show 64' => 'Show in detail message with id 64.',
'drush watchdog-show "cron run succesful"' => 'Show a listing of most recent 10 messages containing the string "cron run succesful".',
'drush watchdog-show --count=46' => 'Show a listing of most recent 46 messages.',
'drush watchdog-show --severity=notice' => 'Show a listing of most recent 10 messages with a severity of notice.',
'drush watchdog-show --type=php' => 'Show a listing of most recent 10 messages of type php.',
'drush watchdog-show --tail --extended' => 'Show a listing of most recent 10 messages with extended information about each one and continue showing messages as they are registered in the watchdog.',
'drush watchdog-show --tail --sleep-delay=2' => 'Do a tail of the watchdog with a delay of two seconds between each poll to the database.',
),
'outputformat' => array(
'default' => 'table',
'pipe-format' => 'var_export',
'field-labels' => array('wid' => 'ID', 'type' => 'Type', 'message' => 'Message', 'severity' => 'Severity', 'location' => 'Location', 'hostname' => 'Hostname', 'date' => 'Date', 'username' => 'Username'),
'fields-default' => array('wid', 'date', 'type', 'severity', 'message'),
'column-widths' => array('type' => 8, 'severity' => 8),
'output-data-type' => 'format-table',
),
'aliases' => array('wd-show', 'ws'),
);
$items['watchdog-delete'] = array(
'description' => 'Delete watchdog messages.',
'drupal dependencies' => array('dblog'),
'options' => array(
'severity' => 'Delete messages of a given severity level.',
'type' => 'Delete messages of a given type.',
),
'examples' => array(
'drush watchdog-delete all' => 'Delete all messages.',
'drush watchdog-delete 64' => 'Delete messages with id 64.',
'drush watchdog-delete "cron run succesful"' => 'Delete messages containing the string "cron run succesful".',
'drush watchdog-delete --severity=notice' => 'Delete all messages with a severity of notice.',
'drush watchdog-delete --type=cron' => 'Delete all messages of type cron.',
),
'aliases' => array('wd-del', 'wd-delete'),
);
return $items;
}
/**
* Command callback.
*/
function drush_core_watchdog_list() {
drush_include_engine('drupal', 'environment');
$options['-- types --'] = dt('== message types ==');
$types = drush_watchdog_message_types();
foreach ($types as $key => $type) {
$options[$key] = $type;
}
$options['-- levels --'] = dt('== severity levels ==');
$severities = drush_watchdog_severity_levels();
foreach ($severities as $key => $value) {
$options[$key] = "$value($key)";
}
$option = drush_choice($options, dt('Select a message type or severity level.'));
if ($option === FALSE) {
return drush_user_abort();
}
if (isset($types[$option])) {
drush_set_option('type', $types[$option]);
}
else {
drush_set_option('severity', $option - $ntypes);
}
return drush_core_watchdog_show_many();
}
/**
* Command callback.
*/
function drush_core_watchdog_show($arg = NULL) {
drush_include_engine('drupal', 'environment');
if (is_numeric($arg)) {
return drush_core_watchdog_show_one($arg);
}
else {
return 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) {
drush_set_default_outputformat('key-value-list', array('fields-default' => array('wid', 'type', 'message', 'severity', 'date'),));
$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);
return array($result->wid => (array)$result);
}
/**
* 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);
$extended = drush_get_option('extended', FALSE);
$where = core_watchdog_query($type, $severity, $filter);
if ($where === FALSE) {
return drush_log(dt('Aborting.'));
}
$rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], 0, $count, 'wid', 'DESC');
if ($rsc === FALSE) {
return drush_log(dt('Aborting.'));
}
$table = array();
while ($result = drush_db_fetch_object($rsc)) {
$row = core_watchdog_format_result($result, $extended);
$table[$row->wid] = (array)$row;
}
if (empty($table) && !$tail) {
drush_log(dt('No log messages available.'), LogLevel::OK);
return array();
}
else {
drush_log(dt('Most recent !count watchdog log messages:', array('!count' => $count)));
}
if ($tail) {
$field_list = array('wid' => 'ID', 'date' => 'Date', 'severity' => 'Severity', 'type' => 'Type', 'message' => 'Message');
$table = array_reverse($table);
$table_rows = drush_rows_of_key_value_to_array_table($table, $field_list, array());
$tbl = drush_print_table($table_rows, TRUE);
// Reuse the table object to display each line generated while in tail mode.
// To make it possible some hacking is done on the object:
// remove the header and reset the rows on each iteration.
$tbl->_headers = NULL;
// Obtain the last wid. If the table has no rows, start at 0.
if (count($table_rows) > 1) {
$last = array_pop($table_rows);
$last_wid = $last[0];
}
else {
$last_wid = 0;
}
// Adapt the where snippet.
if ($where['where'] != '') {
$where['where'] .= ' AND ';
}
$where['where'] .= 'wid > :wid';
// sleep-delay
$sleep_delay = drush_get_option('sleep-delay', 1);
while (TRUE) {
$where['args'][':wid'] = $last_wid;
$table = array();
// Reset table rows.
$tbl->_data = array();
$rsc = drush_db_select('watchdog', '*', $where['where'], $where['args'], NULL, NULL, 'wid', 'ASC');
while ($result = drush_db_fetch_object($rsc)) {
$row = core_watchdog_format_result($result, $extended);
$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);
}
}
return $table;
}
/**
* Format a watchdog database row.
*
* @param $result
* Array. A database result object.
* @param $extended
* Boolean. Return extended message details.
* @return
* Array. The result object with some attributes themed.
*/
function core_watchdog_format_result($result, $extended = FALSE) {
// Severity.
$severities = drush_watchdog_severity_levels();
$result->severity = $severities[$result->severity];
// Date.
$result->date = format_date($result->timestamp, 'custom', 'd/M H:i');
unset($result->timestamp);
// Message.
$variables = $result->variables;
if (is_string($variables)) {
$variables = unserialize($variables);
}
if (is_array($variables)) {
$result->message = strtr($result->message, $variables);
}
unset($result->variables);
$message_length = 188;
// Print all the data available
if ($extended) {
// Possible empty values.
if (empty($result->link)) {
unset($result->link);
}
if (empty($result->referer)) {
unset($result->referer);
}
// Username.
if ($account = user_load($result->uid)) {
$result->username = $account->name;
}
else {
$result->username = dt('Anonymous');
}
unset($result->uid);
$message_length = PHP_INT_MAX;
}
if (drush_drupal_major_version() >= 8) {
$result->message = Unicode::truncate(strip_tags(Html::decodeEntities($result->message)), $message_length, FALSE, FALSE);
}
else {
$result->message = truncate_utf8(strip_tags(decode_entities($result->message)), $message_length, FALSE, FALSE);
}
return $result;
}
/**
* Command callback.
*
* @param $arg
* The id of the message to delete or 'all'.
*/
function drush_core_watchdog_delete($arg = NULL) {
drush_include_engine('drupal', 'environment');
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.'), LogLevel::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)), LogLevel::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 ((!isset($arg))&&(!isset($type))&&(!isset($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)), LogLevel::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 = drush_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 (isset($severity)) {
$severities = drush_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);
}

View file

@ -0,0 +1,364 @@
<?php
/**
* @file
* Functions for the generate makefile command.
*/
use Drush\Log\LogLevel;
/**
* Generate the actual contents of the .make file.
*/
function _drush_make_generate_makefile_contents($projects, $libraries = array(), $core_version = NULL, $defaults = array()) {
if (is_null($core_version)) {
$core_version = drush_get_drupal_core_compatibility();
}
$header = array();
$header[] = '; This file was auto-generated by drush make';
$header['core'] = $core_version;
$header['api'] = MAKE_API;
$header[] = '';
if (!empty($defaults)) {
_drush_make_generate_defaults($defaults, $header);
$header[] = '';
}
$header[] = '; Core';
return _drush_make_generate_makefile_body($projects, $header) . _drush_make_generate_makefile_body($libraries);
}
function _drush_make_generate_makefile_body($projects, $output = array()) {
$custom = FALSE;
$previous_type = 'core';
if (isset($projects)) {
foreach ($projects as $name => $project) {
$type = (isset($project['type']) && ($project['type'] == 'library')) ? 'libraries' : 'projects';
if ($previous_type != $project['_type']) {
$previous_type = $project['_type'];
$output[] = '; ' . ucfirst($previous_type) . 's';
}
unset($project['_type']);
if (!$project && is_string($name)) {
$output[] = $type . '[] = "' . $name . '"';
continue;
}
$base = $type . '[' . $name . ']';
if (isset($project['custom_download'])) {
$custom = TRUE;
$output[] = '; Please fill the following out. Type may be one of get, git, bzr or svn,';
$output[] = '; and url is the url of the download.';
$output[$base . '[download][type]'] = '""';
$output[$base . '[download][url]'] = '""';
unset($project['custom_download']);
}
$output = array_merge($output, _drush_make_generate_lines($base, $project));
$output[] = '';
}
}
$string = '';
foreach ($output as $k => $v) {
if (!is_numeric($k)) {
$string .= $k . ' = ' . $v;
}
else {
$string .= $v;
}
$string .= "\n";
}
if ($custom) {
drush_log(dt('Some of the properties in your makefile will have to be manually edited. Please do that now.'), LogLevel::WARNING);
}
return $string;
}
/**
* Write a makefile based on data parsed from a previous makefile.
*
* @param $file
* The path to the file to write our generated makefile to, or TRUE to
* print to the terminal.
* @param $makefile
* A makefile on which to base our generated one.
*/
function make_generate_from_makefile($file, $makefile) {
if (!$info = make_parse_info_file($makefile)) {
return drush_set_error('MAKE_GENERATE_FAILED_PARSE', dt('Failed to parse makefile :makefile.', array(':makefile' => $makefile)));
}
$projects = drush_get_option('DRUSH_MAKE_PROJECTS', FALSE);
if ($projects === FALSE) {
$projects = make_prepare_projects(FALSE, $info);
if (isset($projects['contrib'])) {
$projects = array_merge($projects['core'], $projects['contrib']);
}
}
$defaults = isset($info['defaults']) ? $info['defaults'] : array();
$core = current($projects);
$core = $core['core'];
foreach ($projects as $name => $project) {
// If a specific revision was requested, do not set the version.
if (!isset($project['revision'])) {
$projects[$name]['version'] = isset($project['download']['full_version']) ? $project['download']['full_version'] : '';
if ($project['type'] != 'core' && strpos($projects[$name]['version'], $project['core']) === 0) {
$projects[$name]['version'] = substr($projects[$name]['version'], strlen($project['core'] . '-'));
}
}
else {
unset($projects[$name]['version']);
}
$projects[$name]['_type'] = $project['type'];
if ($project['download']['type'] == 'git') {
drush_make_resolve_git_refs($projects[$name]);
}
// Don't clutter the makefile with defaults
if (is_array($defaults)) {
foreach ($defaults as $type => $defs) {
if ($type == 'projects') {
foreach ($defs as $key => $value) {
if (isset($project[$key]) && $project[$key] == $value) {
unset($projects[$name][$key]);
}
}
}
}
}
if ($project['name'] == $name) {
unset($projects[$name]['name']);
}
if ($project['type'] == 'module' && !isset($info[$name]['type'])) {
unset($projects[$name]['type']); // Module is the default
}
if (!(isset($project['download']['type'])) || ($project['download']['type'] == 'pm')) {
unset($projects[$name]['download']); // PM is the default
}
$ignore = array('build_path', 'contrib_destination', 'core', 'make_directory', 'l10n_url', 'download_type');
foreach ($ignore as $key) {
unset($projects[$name][$key]);
}
// Remove the location if it's the default.
if ($projects[$name]['location'] == 'https://updates.drupal.org/release-history') {
unset($projects[$name]['location']);
}
// Remove empty entries (e.g. 'directory_name')
$projects[$name] = _make_generate_array_filter($projects[$name]);
}
$libraries = drush_get_option('DRUSH_MAKE_LIBRARIES', FALSE);
if ($libraries === FALSE) {
$libraries = isset($info['libraries']) ? $info['libraries'] : array();
}
if (is_array($libraries)) {
foreach ($libraries as $name => $library) {
$libraries[$name]['type'] = 'library';
$libraries[$name]['_type'] = 'librarie';
if ($library['download']['type'] == 'git') {
drush_make_resolve_git_refs($libraries[$name]);
}
}
}
$contents = make_generate_makefile_contents($projects, $libraries, $core, $defaults);
// Write or print our makefile.
$file = $file !== TRUE ? $file : NULL;
make_generate_print($contents, $file);
}
/**
* Resolve branches and revisions for git-based projects.
*/
function drush_make_resolve_git_refs(&$project) {
if (!isset($project['download']['branch'])) {
$project['download']['branch'] = drush_make_resolve_git_branch($project);
}
if (!isset($project['download']['revision'])) {
$project['download']['revision'] = drush_make_resolve_git_revision($project);
}
}
/**
* Resolve branch for a git-based project.
*/
function drush_make_resolve_git_branch($project) {
drush_log(dt('Resolving default branch for repo at: :repo', array(':repo' => $project['download']['url'])));
if (drush_shell_exec("git ls-remote %s HEAD", $project['download']['url'])) {
$head_output = drush_shell_exec_output();
list($head_commit) = explode("\t", $head_output[0]);
drush_log(dt('Scanning branches in repo at: :repo', array(':repo' => $project['download']['url'])));
drush_shell_exec("git ls-remote --heads %s", $project['download']['url']);
$heads_output = drush_shell_exec_output();
$branches = array();
foreach ($heads_output as $key => $head) {
list($commit, $ref) = explode("\t", $head);
$branches[$commit] = explode("/", $ref)[2];
}
$branch = $branches[$head_commit];
drush_log(dt('Resolved git branch to: :branch', array(':branch' => $branch)));
return $branch;
}
else {
drush_log(dt('Could not resolve branch for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning');
}
}
/**
* Resolve revision for a git-based project.
*/
function drush_make_resolve_git_revision($project) {
drush_log(dt('Resolving head commit on `:branch` branch for repo at: :repo', array(':branch' => $project['download']['branch'], ':repo' => $project['download']['url'])));
if (drush_shell_exec("git ls-remote %s %s", $project['download']['url'], $project['download']['branch'])) {
$head_output = drush_shell_exec_output();
list($revision) = explode("\t", $head_output[0]);
drush_log(dt('Resolved git revision to: :revision', array(':revision' => $revision)));
return $revision;
}
else {
drush_log(dt('Could not resolve head commit for `:project` using git repo at :repo', array(':project' => $project['name'], ':repo' => $project['download']['url'])), 'warning');
}
}
/**
* Generate makefile contents in the appropriate format.
*/
function make_generate_makefile_contents($projects, $libraries = array(), $core = NULL, $defaults = array()) {
$format = drush_get_option('format', 'yaml');
$func = "make_generate_makefile_contents_$format";
if (function_exists($func)) {
$contents = call_user_func($func, $projects, $libraries, $core, $defaults);
}
else {
return drush_set_error('MAKE_UNKNOWN_OUTPUT_FORMAT', dt('Generating makefiles in the :format output format is not yet supported. Implement :func() to add such support.', array(':format' => $format, ':func' => $func)));
}
return $contents;
}
/**
* Generate makefile contents in (legacy) INI format.
*/
function make_generate_makefile_contents_ini($projects, $libraries, $core, $defaults) {
return _drush_make_generate_makefile_contents($projects, $libraries, $core, $defaults);
}
/**
* Generate makefile contents in YAML format.
*/
function make_generate_makefile_contents_yaml($projects, $libraries, $core, $defaults) {
$info = array(
'core' => $core,
'api' => MAKE_API,
'defaults' => $defaults,
'projects' => $projects,
'libraries' => $libraries,
);
$info = _make_generate_array_filter($info);
$info = _make_generate_array_filter_key('_type', $info);
$dumper = drush_load_engine('outputformat', 'yaml');
$yaml = $dumper->format($info, array());
return $yaml;
}
/**
* Helper function to recursively remove empty values from an array (but not
* '0'!).
*/
function _make_generate_array_filter($haystack) {
foreach ($haystack as $key => $value) {
if (is_array($value)) {
$haystack[$key] = _make_generate_array_filter($haystack[$key]);
}
if (empty($value) && $value !== '0') {
unset($haystack[$key]);
}
}
return $haystack;
}
/**
* Helper function to recursively remove elements matching a specific key from an array.
*/
function _make_generate_array_filter_key($needle, $haystack) {
foreach ($haystack as $key => $value) {
if ($key === $needle) {
unset($haystack[$key]);
}
elseif (is_array($value)) {
$haystack[$key] = _make_generate_array_filter_key($needle, $haystack[$key]);
}
}
return $haystack;
}
/**
* Print the generated makefile to the terminal, or write it to a file.
*
* @param $contents
* The formatted contents of a makefile.
* @param $file
* (optional) The path to write the makefile.
*/
function make_generate_print($contents, $file = NULL) {
if (!$file) {
drush_print($contents);
}
elseif (file_put_contents($file, $contents)) {
drush_log(dt("Wrote .make file @file", array('@file' => $file)), LogLevel::OK);
}
else {
make_error('FILE_ERROR', dt("Unable to write .make file !file", array('!file' => $file)));
}
}
/**
* Utility function to generate the line or lines for a key/value pair in the
* make file.
*
* @param $base
* The base for the configuration lines. Values will be appended to it as
* [$key] = $value, or if value is an array itself it will expand into as many
* lines as required.
* @param $values
* May be a single value or an array.
* @return
* An array of strings that represent lines for the make file.
*/
function _drush_make_generate_lines($base, $values) {
$output = array();
if (is_array($values)) {
foreach ($values as $key => $value) {
$newbase = $base . '[' . $key . ']';
$output = array_merge($output, _drush_make_generate_lines($newbase, $value));
}
}
else {
$output[$base] = '"' . $values . '"';
}
return $output;
}
function _drush_make_generate_defaults($defaults, &$output = array()) {
$output[] = '; Defaults';
foreach ($defaults as $name => $project) {
$type = 'defaults';
if (!$project && is_string($name)) {
$output[] = $type . '[] = "' . $name . '"';
continue;
}
$base = $type . '[' . $name . ']';
$output = array_merge($output, _drush_make_generate_lines($base, $project));
}
}

View file

@ -0,0 +1,308 @@
<?php
/**
* @file
* Functions for the generate makefile command.
*/
include_once DRUSH_DRUPAL_CORE . '/includes/install.inc';
include_once drupal_get_path('module', 'system') . '/system.install';
include_once 'generate.contents.make.inc';
/**
* Drush callback; generate makefile from the current build.
*/
function drush_make_generate($file = NULL) {
$version_options = _drush_make_generate_get_version_options();
$all_extensions = drush_get_extensions();
list($projects, $libraries) = _drush_make_generate_projects($all_extensions, $version_options);
$core = drush_drupal_major_version() . '.x';
$contents = make_generate_makefile_contents($projects, $libraries, $core);
// Write or print our makefile.
make_generate_print($contents, $file);
}
/**
* Create the $version_options array from the --include-versions and
* --exclude-versions command line options.
*/
function _drush_make_generate_get_version_options() {
// What projects should we pin the versions for?
// Check the command-line options for details.
foreach (array("include", "exclude") as $option) {
$version_options[$option] = drush_get_option("$option-versions");
if ($version_options[$option] !== TRUE) {
$version_options[$option] = array_filter(explode(",", $version_options[$option]));
}
}
return $version_options;
}
/**
* Generate the $projects makefile array for the current site.
*/
function _drush_make_generate_projects($all_extensions, $version_options) {
$release_info = drush_get_engine('release_info');
$projects = array();
$project_libraries = array();
$system_requirements = system_requirements('runtime');
// Update xml expects the drupal version to be expressed as "7.x" or "8.x"
// We used to check $system_requirements['drupal']['value'], but this now
// contains values such as "7.10-dev".
$drupal_major_version = drush_drupal_major_version() . '.x';
$core_project = strtolower($system_requirements['drupal']['title']);
$projects[$core_project] = array('_type' => 'core');
if ($core_project != 'drupal') {
$projects[$core_project]['custom_download'] = TRUE;
$projects[$core_project]['type'] = 'core';
}
else {
// Drupal core - we can determine the version if required.
if (_drush_generate_track_version("drupal", $version_options)) {
$projects[$core_project]["version"] = drush_drupal_version();
}
}
$install_profile = drush_drupal_major_version() >= 7 ? drupal_get_profile() : variable_get('install_profile', '');
if (!in_array($install_profile, array('default', 'standard', 'minimal', 'testing')) && $install_profile != '') {
$projects[$install_profile]['type']
= $projects[$install_profile]['_type'] = 'profile';
$request = array(
'name' => $install_profile,
'drupal_version' => $drupal_major_version,
);
if (!$release_info->checkProject($request, 'profile')) {
$projects[$install_profile]['custom_download'] = TRUE;
}
}
// Iterate installed projects to build $projects array.
$extensions = $all_extensions;
$project_info = drush_get_projects($extensions);
foreach ($project_info as $name => $project) {
// Discard the extensions within this project. At the end $extensions will
// contain only extensions part of custom projects (not from drupal.org or
// other update service).
foreach ($project['extensions'] as $ext) {
unset($extensions[$ext]);
}
if ($name == 'drupal') {
continue;
}
$type = $project['type'];
// Discard projects with all modules disabled.
if (($type == 'module') && (!$project['status'])) {
continue;
}
$projects[$name] = array('_type' => $type);
// Check the project is on drupal.org or its own update service.
$request = array(
'name' => $name,
'drupal_version' => $drupal_major_version,
);
if (isset($project['status url'])) {
$request['status url'] = $project['status url'];
$projects[$name]['location'] = $project['status url'];
}
if (!$release_info->checkProject($request, $type)) {
// It is not a project on drupal.org neither an external update service.
$projects[$name]['type'] = $type;
$projects[$name]['custom_download'] = TRUE;
}
// Add 'subdir' if the project is installed in a non-default location.
if (isset($project['path'])) {
$projects[$name] += _drush_generate_makefile_check_path($project);
}
// Add version number if this project's version is to be tracked.
if (_drush_generate_track_version($name, $version_options) && $project["version"]) {
$version = preg_replace("/^" . drush_get_drupal_core_compatibility() . "-/", "", $project["version"]);
// Strip out MINOR+GIT_COMMIT strings for dev releases.
if (substr($version, -4) == '-dev' && strpos($version, '+')) {
$version = substr($version, 0, strrpos($version, '.')) . '.x-dev';
}
$projects[$name]['version'] = $version;
}
foreach ($project['extensions'] as $extension_name) {
_drush_make_generate_add_patch_files($projects[$name], _drush_extension_get_path($all_extensions[$extension_name]));
}
}
// Add a project for each unknown extension.
foreach ($extensions as $name => $extension) {
list($project_name, $project_data) = _drush_generate_custom_project($name, $extension, $version_options);
$projects[$project_name] = $project_data;
}
// Add libraries.
if (function_exists('libraries_get_libraries')) {
$libraries = libraries_get_libraries();
foreach ($libraries as $library_name => $library_path) {
$path = explode('/', $library_path);
$project_libraries[$library_name] = array(
'directory_name' => $path[(count($path) - 1)],
'custom_download' => TRUE,
'type' => 'library',
'_type' => 'librarie', // For plural.
);
}
}
return array($projects, $project_libraries);
}
/**
* Record any patches that were applied to this project
* per information stored in PATCHES.txt.
*/
function _drush_make_generate_add_patch_files(&$project, $location) {
$patchfile = DRUPAL_ROOT . '/' . $location . '/PATCHES.txt';
if (is_file($patchfile)) {
foreach (file($patchfile) as $line) {
if (substr($line, 0, 2) == '- ') {
$project['patch'][] = trim(substr($line, 2));
}
}
}
}
/**
* Create a project record for an extension not downloaded from drupal.org
*/
function _drush_generate_custom_project($name, $extension, $version_options) {
$project['_type'] = drush_extension_get_type($extension);
$project['type'] = drush_extension_get_type($extension);
$location = drush_extension_get_path($extension);
// To start off, we will presume that our custom extension is
// stored in a folder named after its project, and there are
// no subfolders between the .info file and the project root.
$project_name = basename($location);
drush_shell_cd_and_exec($location, 'git rev-parse --git-dir 2> ' . drush_bit_bucket());
$output = drush_shell_exec_output();
if (!empty($output)) {
$git_dir = $output[0];
// Find the actual base of the git repository.
$repo_root = $git_dir == ".git" ? $location : dirname($git_dir);
// If the repository root is at the drupal root or some parent
// of the drupal root, or some other location that could not
// pausibly be a project, then there is nothing we can do.
// (We can't tell Drush make to download some sub-part of a repo,
// can we?)
if ($repo_project_name = _drush_generate_validate_repo_location($repo_root)) {
$project_name = $repo_project_name;
drush_shell_cd_and_exec($repo_root, 'git remote show origin');
$output = drush_shell_exec_output();
foreach ($output as $line) {
if (strpos($line, "Fetch URL:") !== FALSE) {
$url = preg_replace('/ *Fetch URL: */', '', $line);
if (!empty($url)) {
// We use the unconventional-looking keys
// `download][type` and `download][url` so that
// we can produce output that appears to be two-dimensional
// arrays from a single-dimensional array.
$project['download][type'] = 'git';
$project['download][url'] = $url;
// Fill in the branch as well.
drush_shell_cd_and_exec($repo_root, 'git branch');
$output = drush_shell_exec_output();
foreach ($output as $line) {
if ($line{0} == '*') {
$branch = substr($line, 2);
if ($branch != "master") {
$project['download][branch'] = $branch;
}
}
}
// Put in the commit hash.
drush_shell_cd_and_exec($repo_root, 'git log');
$output = drush_shell_exec_output();
if (substr($output[0], 0, 7) == "commit ") {
$revision = substr($output[0], 7);
if (_drush_generate_track_version($project_name, $version_options)) {
$project['download][revision'] = $revision;
}
}
// Add patch files, if any.
_drush_make_generate_add_patch_files($project, $repo_root);
}
}
}
}
}
// If we could not figure out where the extension came from, then give up and
// flag it as a "custom" download.
if (!isset($project['download][type'])) {
$project['custom_download'] = TRUE;
}
return array($project_name, $project);
}
/**
* If the user has checked in the Drupal root, or the 'sites/all/modules'
* folder into a git repository, then we do not want to confuse that location
* with a "project".
*/
function _drush_generate_validate_repo_location($repo_root) {
$project_name = basename($repo_root);
// The Drupal root, or any folder immediately inside the Drupal
// root cannot be a project location.
if ((strlen(DRUPAL_ROOT) >= strlen($repo_root)) || (dirname($repo_root) == DRUPAL_ROOT)) {
return NULL;
}
// Also exclude sites/* and sites/*/{modules,themes} and profile/* and
// profile/*/{modules,themes}.
return $project_name;
}
/**
* Helper function to determine if a given project is to have its version
* tracked.
*/
function _drush_generate_track_version($project, $version_options) {
// A. If --exclude-versions has been specified:
// A.a. if it's a boolean, check the --include-versions option.
if ($version_options["exclude"] === TRUE) {
// A.a.1 if --include-versions has been specified, ensure it's an array.
if (is_array($version_options["include"])) {
return in_array($project, $version_options["include"]);
}
// A.a.2 If no include array, then we're excluding versions for ALL
// projects.
return FALSE;
}
// A.b. if --exclude-versions is an array with items, check this project is in
// it: if so, then return FALSE.
elseif (is_array($version_options["exclude"]) && count($version_options["exclude"])) {
return !in_array($project, $version_options["exclude"]);
}
// B. If by now no --exclude-versions, but --include-versions is an array,
// examine it for this project.
if (is_array($version_options["include"]) && count($version_options["include"])) {
return in_array($project, $version_options["include"]);
}
// If none of the above conditions match, include version number by default.
return TRUE;
}
/**
* Helper function to check for a non-default installation location.
*/
function _drush_generate_makefile_check_path($project) {
$info = array();
$type = $project['type'];
$path = dirname($project['path']);
// Check to see if the path is in a subdir sites/all/modules or
// profiles/profilename/modules
if (preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path) || preg_match('@^sites/[a-zA-Z0-9_]*/' . $type . 's/..*@', $path)) {
$subdir = preg_replace(array('@^[a-zA-Z0-9_]*/[a-zA-Z0-9_]*/' . $type . 's/*@', "@/$name" . '$@'), '', $path);
if (!empty($subdir)) {
$info['subdir'] = $subdir;
}
}
return $info;
}

View file

@ -0,0 +1,13 @@
<?php
/**
* @file
* make-lock command implementation.
*/
/**
* Command callback for make-lock.
*/
function drush_make_lock($makefile) {
make_generate_from_makefile(drush_get_option('result-file'), $makefile);
}

View file

@ -0,0 +1,548 @@
<?php
/**
* @file
* Download-specific functions for Drush Make.
*/
use Drush\Log\LogLevel;
/**
* Downloads the given package to the destination directory.
*
* @return mixed
* The destination path on success, FALSE on failure.
*/
function make_download_factory($name, $type, $download, $download_location) {
$function = 'make_download_' . $download['type'];
if (function_exists($function)) {
return $function($name, $type, $download, $download_location);
}
else {
return FALSE;
}
}
/**
* Download project using drush's pm-download command.
*/
function make_download_pm($name, $type, $download, $download_location) {
$full_project_version = $name . '-' . $download['full_version'];
$options = array(
'destination' => dirname($download_location),
'yes' => TRUE,
'package-handler' => 'wget',
'source' => $download['status url'],
// This is only relevant for profiles, but we generally want the variant to
// be 'profile-only' so we don't end up with extra copies of core.
'variant' => $type == 'core' ? 'full' : $download['variant'],
'cache' => TRUE,
);
if ($type == 'core') {
$options['drupal-project-rename'] = basename($download_location);
}
if (drush_get_option('no-cache', FALSE)) {
unset($options['cache']);
}
$backend_options = array();
if (!drush_get_option(array('verbose', 'debug'), FALSE)) {
$backend_options['integrate'] = TRUE;
$backend_options['log'] = FALSE;
}
// Perform actual download with `drush pm-download`.
$return = drush_invoke_process('@none', 'pm-download', array($full_project_version), $options, $backend_options);
if (empty($return['error_log'])) {
// @todo Report the URL we used for download. See
// http://drupal.org/node/1452672.
drush_log(dt('@project downloaded.', array('@project' => $full_project_version)), LogLevel::OK);
}
}
/**
* Downloads a file to the specified location.
*
* @return mixed
* The destination directory on success, FALSE on failure.
*/
function make_download_file($name, $type, $download, $download_location, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
if ($filename = _make_download_file($download['url'], $cache_duration)) {
if (!drush_get_option('ignore-checksums') && !_make_verify_checksums($download, $filename)) {
return FALSE;
}
drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
$download_filename = isset($download['filename']) ? $download['filename'] : '';
$subtree = isset($download['subtree']) ? $download['subtree'] : NULL;
return make_download_file_unpack($filename, $download_location, $download_filename, $subtree);
}
make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
return FALSE;
}
/**
* Wrapper to drush_download_file().
*
* @param string $download
* The url of the file to download.
* @param int $cache_duration
* The time in seconds to cache the resultant download.
*
* @return string
* The location of the downloaded file, or FALSE on failure.
*/
function _make_download_file($download, $cache_duration = DRUSH_CACHE_LIFETIME_DEFAULT) {
if (drush_get_option('no-cache', FALSE)) {
$cache_duration = 0;
}
$tmp_path = make_tmp();
// Ensure that we aren't including the querystring when generating a filename
// to save our download to.
$file = basename(current(explode('?', $download, 2)));
return drush_download_file($download, $tmp_path . '/' . $file, $cache_duration);
}
/**
* Unpacks a file to the specified download location.
*
* @return mixed
* The download location on success, FALSE on failure.
*/
function make_download_file_unpack($filename, $download_location, $name, $subtree = NULL) {
$success = FALSE;
if (drush_file_is_tarball($filename)) {
$tmp_location = drush_tempdir();
if (!drush_tarball_extract($filename, $tmp_location)) {
return FALSE;
}
if ($subtree) {
$tmp_location .= '/' . $subtree;
if (!file_exists($tmp_location)) {
return drush_set_error('DRUSH_MAKE_SUBTREE_NOT_FOUND', dt('Directory !subtree not found within !file', array('!subtree' => $subtree, '!file' => $filename)));
}
}
else {
$files = scandir($tmp_location);
unset($files[0]); // . directory
unset($files[1]); // .. directory
if ((count($files) == 1) && is_dir($tmp_location . '/' . current($files))) {
$tmp_location .= '/' . current($files);
}
}
$success = drush_move_dir($tmp_location, $download_location, TRUE);
// Remove the tarball.
if (file_exists($filename)) {
drush_delete_dir($filename, TRUE);
}
}
else {
// If this is an individual file, and no filename has been specified,
// assume the original name.
if (is_file($filename) && !$name) {
$name = basename($filename);
}
// The destination directory has already been created by
// findDownloadLocation().
$destination = $download_location . ($name ? '/' . $name : '');
$success = drush_move_dir($filename, $destination, TRUE);
}
return $success ? $download_location : FALSE;
}
/**
* Move a downloaded and unpacked file or directory into place.
*/
function _make_download_file_move($tmp_path, $filename, $download_location, $subtree = NULL) {
$lines = drush_scan_directory($tmp_path, '/./', array('.', '..'), 0, FALSE, 'filename', 0, TRUE);
$main_directory = basename($download_location);
if (count($lines) == 1) {
$directory = array_shift($lines);
if ($directory->basename != $main_directory) {
drush_move_dir($directory->filename, $tmp_path . DIRECTORY_SEPARATOR . $main_directory, TRUE);
}
drush_copy_dir($tmp_path . DIRECTORY_SEPARATOR . $main_directory . DIRECTORY_SEPARATOR . $subtree, $download_location, FILE_EXISTS_OVERWRITE);
drush_delete_dir($tmp_path, TRUE);
}
elseif (count($lines) > 1) {
drush_delete_dir($download_location, TRUE);
drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $subtree, $download_location, TRUE);
}
// Remove the tarball.
if (file_exists($filename)) {
drush_delete_dir($filename, TRUE);
}
if (file_exists($tmp_path)) {
drush_delete_dir($tmp_path, TRUE);
}
return TRUE;
}
/**
* For backwards compatibility.
*/
function make_download_get($name, $type, $download, $download_location) {
return make_download_file($name, $type, $download, $download_location);
}
/**
* Copies a folder the specified location.
*
* @return mixed
* The TRUE on success, FALSE on failure.
*/
function make_download_copy($name, $type, $download, $download_location) {
if ($folder = _make_download_copy($download['url'])) {
drush_log(dt('@project copied from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
return drush_copy_dir($folder, $download_location, FILE_EXISTS_OVERWRITE);
}
make_error('COPY_ERROR', dt('Unable to copy @project from @url.', array('@project' => $name, '@url' => $download['url'])));
return FALSE;
}
/**
* Wrapper to drush_download_copy().
*
* @param string $folder
* The location of the folder to copy.
*
* @return string
* The location of the folder, or FALSE on failure.
*/
function _make_download_copy($folder) {
if (substr($folder, 0, 7) == 'file://') {
$folder = substr($folder, 7);
}
if (is_dir($folder)) {
return $folder;
}
return FALSE;
}
/**
* Checks out a git repository to the specified download location.
*
* Allowed parameters in $download, in order of precedence:
* - 'tag'
* - 'revision'
* - 'branch'
*
* This will also attempt to write out release information to the
* .info file if the 'no-gitinfofile' option is FALSE. If
* $download['full_version'] is present, this will be used, otherwise,
* version will be set in this order of precedence:
* - 'tag'
* - 'branch'
* - 'revision'
*
* @return mixed
* The download location on success, FALSE otherwise.
*/
function make_download_git($name, $type, $download, $download_location) {
$tmp_path = make_tmp();
$wc = _get_working_copy_option($download);
$checkout_after_clone = TRUE;
// If no download URL specified, assume anonymous clone from git.drupal.org.
$download['url'] = isset($download['url']) ? $download['url'] : "http://git.drupal.org/project/$name.git";
// If no working-copy download URL specified, assume it is the same.
$download['wc_url'] = isset($download['wc_url']) ? $download['wc_url'] : $download['url'];
// If not a working copy, and if --no-cache has not been explicitly
// declared, create a new git reference cache of the remote repository,
// or update the existing cache to fetch recent changes.
// @see package_handler_download_project()
$cache = !$wc && !drush_get_option('no-cache', FALSE);
if ($cache && ($git_cache = drush_directory_cache('git'))) {
$project_cache = $git_cache . '/' . $name . '-' . md5($download['url']);
// Set up a new cache, if it doesn't exist.
if (!file_exists($project_cache)) {
$command = 'git clone --mirror';
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
$command .= ' %s %s';
drush_shell_cd_and_exec($git_cache, $command, $download['url'], $project_cache);
}
else {
// Update the --mirror clone.
drush_shell_cd_and_exec($project_cache, 'git remote update');
}
$git_cache = $project_cache;
}
// Use working-copy download URL if --working-copy specified.
$url = $wc ? $download['wc_url'] : $download['url'];
$tmp_location = drush_tempdir() . '/' . basename($download_location);
$command = 'git clone %s %s';
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
if ($cache) {
$command .= ' --reference ' . drush_escapeshellarg($git_cache);
}
// the shallow clone option is only applicable to git entries which reference a tag or a branch
if (drush_get_option('shallow-clone', FALSE) &&
(!empty($download['tag']) || !empty($download['branch']))) {
$branch = (!empty($download['branch']) ? $download['branch'] : $download['tag']);
$command .= " --depth=1 --branch=${branch}";
// since the shallow copy option automatically "checks out" the requested branch, no further
// actions are needed after the clone command
$checkout_after_clone = FALSE;
}
// Before we can checkout anything, we need to clone the repository.
if (!drush_shell_exec($command, $url, $tmp_location)) {
make_error('DOWNLOAD_ERROR', dt('Unable to clone @project from @url.', array('@project' => $name, '@url' => $url)));
return FALSE;
}
drush_log(dt('@project cloned from @url.', array('@project' => $name, '@url' => $url)), LogLevel::OK);
if ($checkout_after_clone) {
// Get the current directory (so we can move back later).
$cwd = getcwd();
// Change into the working copy of the cloned repo.
chdir($tmp_location);
// We want to use the most specific target possible, so first try a refspec.
if (!empty($download['refspec'])) {
if (drush_shell_exec("git fetch %s %s", $url, $download['refspec'])) {
drush_log(dt("Fetched refspec !refspec.", array('!refspec' => $download['refspec'])), LogLevel::OK);
if (drush_shell_exec("git checkout FETCH_HEAD")) {
drush_log(dt("Checked out FETCH_HEAD."), LogLevel::INFO);
}
}
else {
make_error('DOWNLOAD_ERROR', dt("Unable to fetch the refspec @refspec from @project.", array('@refspec' => $download['refspec'], '@project' => $name)));
}
}
// If there wasn't a refspec, try a tag.
elseif (!empty($download['tag'])) {
// @TODO: change checkout to refs path.
if (drush_shell_exec("git checkout %s", 'refs/tags/' . $download['tag'])) {
drush_log(dt("Checked out tag @tag.", array('@tag' => $download['tag'])), LogLevel::OK);
}
else {
make_error('DOWNLOAD_ERROR', dt("Unable to check out tag @tag.", array('@tag' => $download['tag'])));
}
}
// If there wasn't a tag, try a specific revision hash.
elseif (!empty($download['revision'])) {
if (drush_shell_exec("git checkout %s", $download['revision'])) {
drush_log(dt("Checked out revision @revision.", array('@revision' => $download['revision'])), LogLevel::OK);
}
else {
make_error('DOWNLOAD_ERROR', dt("Unable to checkout revision @revision", array('@revision' => $download['revision'])));
}
}
// If not, see if we at least have a branch.
elseif (!empty($download['branch'])) {
if (drush_shell_exec("git checkout %s", $download['branch']) && (trim(implode(drush_shell_exec_output())) != '')) {
drush_log(dt("Checked out branch @branch.", array('@branch' => $download['branch'])), LogLevel::OK);
}
elseif (drush_shell_exec("git checkout -b %s %s", $download['branch'], 'origin/' . $download['branch'])) {
drush_log(dt('Checked out branch origin/@branch.', array('@branch' => $download['branch'])), LogLevel::OK);
}
else {
make_error('DOWNLOAD_ERROR', dt('Unable to check out branch @branch.', array('@branch' => $download['branch'])));
}
}
if (!empty($download['submodule'])) {
$command = 'git submodule update';
foreach ($download['submodule'] as $option) {
$command .= ' --%s';
}
if (call_user_func_array('drush_shell_exec', array_merge(array($command), $download['submodule']))) {
drush_log(dt('Initialized registered submodules.'), LogLevel::OK);
}
else {
make_error('DOWNLOAD_ERROR', dt('Unable to initialize submodules.'));
}
}
// Move back to last current directory (first line).
chdir($cwd);
}
// Move the directory into the final resting location.
drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE);
return dirname($tmp_location);
}
/**
* Checks out a Bazaar repository to the specified download location.
*
* @return mixed
* The download location on success, FALSE otherwise.
*/
function make_download_bzr($name, $type, $download, $download_location) {
$tmp_path = make_tmp();
$tmp_location = drush_tempdir() . '/' . basename($download_location);
$wc = _get_working_copy_option($download);
if (!empty($download['url'])) {
$args = array();
$command = 'bzr';
if ($wc) {
$command .= ' branch --use-existing-dir';
}
else {
$command .= ' export';
}
if (isset($download['revision'])) {
$command .= ' -r %s';
$args[] = $download['revision'];
}
$command .= ' %s %s';
if ($wc) {
$args[] = $download['url'];
$args[] = $tmp_location;
}
else {
$args[] = $tmp_location;
$args[] = $download['url'];
}
array_unshift($args, $command);
if (call_user_func_array('drush_shell_exec', $args)) {
drush_log(dt('@project downloaded from @url.', array('@project' => $name, '@url' => $download['url'])), LogLevel::OK);
drush_copy_dir($tmp_location, $download_location, FILE_EXISTS_OVERWRITE);
return dirname($download_location);
}
}
else {
$download['url'] = dt("unspecified location");
}
make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
drush_delete_dir(dirname($tmp_location), TRUE);
return FALSE;
}
/**
* Checks out an SVN repository to the specified download location.
*
* @return mixed
* The download location on success, FALSE otherwise.
*/
function make_download_svn($name, $type, $download, $download_location) {
$wc = _get_working_copy_option($download);
if (!empty($download['url'])) {
if (!empty($download['interactive'])) {
$function = 'drush_shell_exec_interactive';
}
else {
$options = ' --non-interactive';
$function = 'drush_shell_exec';
}
if (!isset($download['force']) || $download['force']) {
$options = ' --force';
}
if ($wc) {
$command = 'svn' . $options . ' checkout';
}
else {
$command = 'svn' . $options . ' export';
}
$args = array();
if (isset($download['revision'])) {
$command .= ' -r%s';
$args[] = $download['revision'];
}
$command .= ' %s %s';
$args[] = $download['url'];
$args[] = $download_location;
if (!empty($download['username'])) {
$command .= ' --username %s';
$args[] = $download['username'];
if (!empty($download['password'])) {
$command .= ' --password %s';
$args[] = $download['password'];
}
}
array_unshift($args, $command);
$result = call_user_func_array($function, $args);
if ($result) {
$args = array(
'@project' => $name,
'@command' => $command,
'@url' => $download['url'],
);
drush_log(dt('@project @command from @url.', $args), LogLevel::OK);
return $download_location;
}
else {
$download['url'] = dt("unspecified location");
}
}
else {
make_error('DOWNLOAD_ERROR', dt('Unable to download @project from @url.', array('@project' => $name, '@url' => $download['url'])));
return FALSE;
}
}
/**
* Test that any supplied hash values match the hash of the file content.
*
* Unsupported hash algorithms are reported as failure.
*/
function _make_verify_checksums($info, $filename) {
$hash_algos = array('md5', 'sha1', 'sha256', 'sha512');
// We only have something to do if a key is an
// available function.
if (array_intersect(array_keys($info), $hash_algos)) {
$content = file_get_contents($filename);
foreach ($hash_algos as $algo) {
if (!empty($info[$algo])) {
$hash = _make_hash($algo, $content);
if ($hash !== $info[$algo]) {
$args = array(
'@algo' => $algo,
'@file' => basename($filename),
'@expected' => $info[$algo],
'@hash' => $hash,
);
make_error('DOWNLOAD_ERROR', dt('Checksum @algo verification failed for @file. Expected @expected, received @hash.', $args));
return FALSE;
}
}
}
}
return TRUE;
}
/**
* Calculate the hash of a string for a given algorithm.
*/
function _make_hash($algo, $string) {
switch ($algo) {
case 'md5':
return md5($string);
case 'sha1':
return sha1($string);
default:
return function_exists('hash') ? hash($algo, $string) : '';
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,740 @@
<?php
/**
* @file
* Drush Make processing classes.
*/
use Drush\Log\LogLevel;
/**
* The base project class.
*/
class DrushMakeProject {
/**
* TRUE if make() has been called, otherwise FALSE.
*/
protected $made = FALSE;
/**
* TRUE if download() method has been called successfully, otherwise FALSE.
*/
protected $downloaded = NULL;
/**
* Download location to use.
*/
protected $download_location = NULL;
/**
* Keep track of instances.
*
* @see DrushMakeProject::getInstance()
*/
protected static $self = array();
/**
* Keeps track of projects being processed to prevent recursive conflicts.
*
* Simple array of machine names.
*
* @var array
*/
protected $manifest = array();
/**
* Default to overwrite to allow recursive builds to process properly.
*
* TODO refactor this to be more selective. Ideally a merge would take place
* instead of an overwrite.
*/
protected $overwrite = TRUE;
/**
* Recursively process any makefiles found in downloaded projects.
*/
protected $do_recursion = TRUE;
/**
* Which variant of profiles to download.
*/
protected $variant = 'profile-only';
/**
* Set attributes and retrieve project information.
*/
protected function __construct($project) {
$project['base_contrib_destination'] = $project['contrib_destination'];
foreach ($project as $key => $value) {
$this->{$key} = $value;
}
if (!empty($this->options['working-copy'])) {
$this->download['working-copy'] = TRUE;
}
// Don't recurse when we're using a pre-built profile tarball.
if ($this->variant == 'projects') {
$this->do_recursion = FALSE;
}
}
/**
* Get an instance for the type and project.
*
* @param string $type
* Type of project: core, library, module, profile, or translation.
* @param array $project
* Project information.
*
* @return mixed
* An instance for the project or FALSE if invalid type.
*/
public static function getInstance($type, $project) {
if (!isset(self::$self[$type][$project['name']])) {
$class = 'DrushMakeProject_' . $type;
self::$self[$type][$project['name']] = class_exists($class) ? new $class($project) : FALSE;
}
return self::$self[$type][$project['name']];
}
/**
* Set the manifest array.
*
* @param array $manifest
* An array of projects as generated by `make_projects`.
*/
public function setManifest($manifest) {
$this->manifest = $manifest;
}
/**
* Download a project.
*/
function download() {
$this->downloaded = TRUE;
// In some cases, make_download_factory() is going to need to know the
// full version string of the project we're trying to download. However,
// the version is a project-level attribute, not a download-level
// attribute. So, if we don't already have a full version string in the
// download array (e.g. if it was initialized via the release history XML
// for the PM case), we take the version info from the project-level
// attribute, convert it into a full version string, and stuff it into
// $this->download so that the download backend has access to it, too.
if (!empty($this->version) && empty($this->download['full_version'])) {
$full_version = '';
$matches = array();
// Core needs different conversion rules than contrib.
if (!empty($this->type) && $this->type == 'core') {
// Generally, the version for core is already set properly.
$full_version = $this->version;
// However, it might just be something like '7' or '7.x', in which
// case we need to turn that into '7.x-dev';
if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) {
// If there's no '.x' already, append it.
if (empty($matches[1])) {
$full_version .= '.x';
}
$full_version .= '-dev';
}
}
// Contrib.
else {
// If the version doesn't already define a core version, prepend it.
if (!preg_match('/^\d+\.x-\d+.*$/', $this->version)) {
// Just find the major version from $this->core so we don't end up
// with version strings like '7.12-2.0'.
$core_parts = explode('.', $this->core);
$full_version = $core_parts[0] . '.x-';
}
$full_version .= $this->version;
// If the project-level version attribute is just a number it's a major
// version.
if (preg_match('/^\d+(\.x)?$/', $this->version, $matches)) {
// If there's no '.x' already, append it.
if (empty($matches[1])) {
$full_version .= '.x';
}
$full_version .= '-dev';
}
}
$this->download['full_version'] = $full_version;
}
$this->download['variant'] = $this->variant;
if (make_download_factory($this->name, $this->type, $this->download, $this->download_location) === FALSE) {
$this->downloaded = FALSE;
}
return $this->downloaded;
}
/**
* Build a project.
*/
function make() {
if ($this->made) {
drush_log(dt('Attempt to build project @project more then once prevented.', array('@project' => $this->name)));
return TRUE;
}
$this->made = TRUE;
if (!isset($this->download_location)) {
$this->download_location = $this->findDownloadLocation();
}
if ($this->download() === FALSE) {
return FALSE;
}
if (!$this->addLockfile($this->download_location)) {
return FALSE;
}
if (!$this->applyPatches($this->download_location)) {
return FALSE;
}
if (!$this->getTranslations($this->download_location)) {
return FALSE;
}
// Handle .info file re-writing (if so desired).
if (!drush_get_option('no-gitinfofile', FALSE) && isset($this->download['type']) && $this->download['type'] == 'git') {
$this->processGitInfoFiles();
}
// Clean-up .git directories.
if (!_get_working_copy_option($this->download)) {
$this->removeGitDirectory();
}
if (!$this->recurse($this->download_location)) {
return FALSE;
}
return TRUE;
}
/**
* Determine the location to download project to.
*/
function findDownloadLocation() {
$this->path = $this->generatePath();
$this->project_directory = !empty($this->directory_name) ? $this->directory_name : $this->name;
$this->download_location = $this->path . '/' . $this->project_directory;
// This directory shouldn't exist yet -- if it does, stop,
// unless overwrite has been set to TRUE.
if (is_dir($this->download_location) && !$this->overwrite) {
return drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location)));
}
elseif ($this->download['type'] === 'pm') {
// pm-download will create the final contrib directory.
drush_mkdir(dirname($this->download_location));
}
else {
drush_mkdir($this->download_location);
}
return $this->download_location;
}
/**
* Rewrite relative URLs and file:/// URLs
*
* relative path -> absolute path using the make_directory
* local file:/// urls -> local paths
*
* @param mixed &$info
* Either an array or a simple url string. The `$info` variable will be
* transformed into an array.
*/
protected function preprocessLocalFileUrl(&$info) {
if (is_string($info)) {
$info = array('url' => $info, 'local' => FALSE);
}
if (!_drush_is_url($info['url']) && !drush_is_absolute_path($info['url'])) {
$info['url'] = $this->make_directory . '/' . $info['url'];
$info['local'] = TRUE;
} elseif (substr($info['url'], 0, 8) == 'file:///') {
$info['url'] = substr($info['url'], 7);
$info['local'] = TRUE;
}
}
/**
* Retrieve and apply any patches specified by the makefile to this project.
*/
function applyPatches($project_directory) {
if (empty($this->patch)) {
return TRUE;
}
$patches_txt = '';
$local_patches = array();
$ignore_checksums = drush_get_option('ignore-checksums');
foreach ($this->patch as $info) {
$this->preprocessLocalFileUrl($info);
// Download the patch.
if ($filename = _make_download_file($info['url'])) {
$patched = FALSE;
$output = '';
// Test each patch style; -p1 is the default with git. See
// http://drupal.org/node/1054616
$patch_levels = array('-p1', '-p0');
foreach ($patch_levels as $patch_level) {
$checked = drush_shell_cd_and_exec($project_directory, 'git --git-dir=. apply --check %s %s --verbose', $patch_level, $filename);
if ($checked) {
// Apply the first successful style.
$patched = drush_shell_cd_and_exec($project_directory, 'git --git-dir=. apply %s %s --verbose', $patch_level, $filename);
break;
}
}
// In some rare cases, git will fail to apply a patch, fallback to using
// the 'patch' command.
if (!$patched) {
foreach ($patch_levels as $patch_level) {
// --no-backup-if-mismatch here is a hack that fixes some
// differences between how patch works on windows and unix.
if ($patched = drush_shell_exec("patch %s --no-backup-if-mismatch -d %s < %s", $patch_level, $project_directory, $filename)) {
break;
}
}
}
if ($output = drush_shell_exec_output()) {
// Log any command output, visible only in --verbose or --debug mode.
drush_log(implode("\n", $output));
}
// Set up string placeholders to pass to dt().
$dt_args = array(
'@name' => $this->name,
'@filename' => basename($filename),
);
if ($patched) {
if (!$ignore_checksums && !_make_verify_checksums($info, $filename)) {
return FALSE;
}
$patch_url = $info['url'];
// If this is a local patch, copy that into place as well.
if ($info['local']) {
$local_patches[] = $info['url'];
// Use a local path for the PATCHES.txt file.
$pathinfo = pathinfo($patch_url);
$patch_url = $pathinfo['basename'];
}
$patches_txt .= '- ' . $patch_url . "\n";
drush_log(dt('@name patched with @filename.', $dt_args), LogLevel::OK);
}
else {
make_error('PATCH_ERROR', dt("Unable to patch @name with @filename.", $dt_args));
}
drush_op('unlink', $filename);
}
else {
make_error('DOWNLOAD_ERROR', 'Unable to download ' . $info['url'] . '.');
return FALSE;
}
}
if (!empty($patches_txt) && !drush_get_option('no-patch-txt') && !file_exists($project_directory . '/PATCHES.txt')) {
$patches_txt = "The following patches have been applied to this project:\n" .
$patches_txt .
"\nThis file was automatically generated by Drush Make (http://drupal.org/project/drush).\n";
file_put_contents($project_directory . '/PATCHES.txt', $patches_txt);
drush_log('Generated PATCHES.txt file for ' . $this->name, LogLevel::OK);
// Copy local patches into place.
foreach ($local_patches as $url) {
$pathinfo = pathinfo($url);
drush_copy_dir($url, $project_directory . '/' . $pathinfo['basename']);
}
}
return TRUE;
}
/**
* Process info files when downloading things from git.
*/
function processGitInfoFiles() {
// Bail out if this isn't hosted on Drupal.org (unless --force-gitinfofile option was specified).
if (!drush_get_option('force-gitinfofile', FALSE) && isset($this->download['url']) && strpos($this->download['url'], 'drupal.org') === FALSE) {
return;
}
// Figure out the proper version string to use based on the .make file.
// Best case is the .make file author told us directly.
if (!empty($this->download['full_version'])) {
$full_version = $this->download['full_version'];
}
// Next best is if we have a tag, since those are identical to versions.
elseif (!empty($this->download['tag'])) {
$full_version = $this->download['tag'];
}
// If we have a branch, append '-dev'.
elseif (!empty($this->download['branch'])) {
$full_version = $this->download['branch'] . '-dev';
}
// Ugh. Not sure what else we can do in this case.
elseif (!empty($this->download['revision'])) {
$full_version = $this->download['revision'];
}
// Probably can never reach this case.
else {
$full_version = 'unknown';
}
// If the version string ends in '.x-dev' do the Git magic to figure out
// the appropriate 'rebuild version' string, e.g. '7.x-1.2+7-dev'.
$matches = array();
if (preg_match('/^(.+).x-dev$/', $full_version, $matches)) {
require_once dirname(__FILE__) . '/../pm/package_handler/git_drupalorg.inc';
$rebuild_version = drush_pm_git_drupalorg_compute_rebuild_version($this->download_location, $matches[1]);
if ($rebuild_version) {
$full_version = $rebuild_version;
}
}
require_once dirname(__FILE__) . '/../pm/pm.drush.inc';
if (drush_shell_cd_and_exec($this->download_location, 'git log -1 --pretty=format:%ct')) {
$output = drush_shell_exec_output();
$datestamp = $output[0];
}
else {
$datestamp = time();
}
drush_pm_inject_info_file_metadata($this->download_location, $this->name, $full_version, $datestamp);
}
/**
* Remove the .git directory from a project.
*/
function removeGitDirectory() {
if (isset($this->download['type']) && $this->download['type'] == 'git' && file_exists($this->download_location . '/.git')) {
drush_delete_dir($this->download_location . '/.git', TRUE);
}
}
/**
* Add a lock file.
*/
function addLockfile($project_directory) {
if (!empty($this->lock)) {
file_put_contents($project_directory . '/.drush-lock-update', $this->lock);
}
return TRUE;
}
/**
* Retrieve translations for this project.
*/
function getTranslations($project_directory) {
static $cache = array();
$langcodes = $this->translations;
if ($langcodes && in_array($this->type, array('core', 'module', 'profile', 'theme'), TRUE)) {
// Support the l10n_path, l10n_url keys from l10n_update. Note that the
// l10n_server key is not supported.
if (isset($this->l10n_path)) {
$update_url = $this->l10n_path;
}
else {
if (isset($this->l10n_url)) {
$l10n_server = $this->l10n_url;
}
else {
$l10n_server = FALSE;
}
if ($l10n_server) {
if (!isset($cache[$l10n_server])) {
$this->preprocessLocalFileUrl($l10n_server);
$l10n_server = $l10n_server['url'];
if ($filename = _make_download_file($l10n_server)) {
$server_info = simplexml_load_string(file_get_contents($filename));
$cache[$l10n_server] = !empty($server_info->update_url) ? $server_info->update_url : FALSE;
}
}
if ($cache[$l10n_server]) {
$update_url = $cache[$l10n_server];
}
else {
make_error('XML_ERROR', dt("Could not retrieve l10n update url for !project.", array('!project' => $this->name)));
return FALSE;
}
}
}
if ($update_url) {
$failed = array();
foreach ($langcodes as $langcode) {
$variables = array(
'%project' => $this->name,
'%release' => $this->download['full_version'],
'%core' => $this->core,
'%language' => $langcode,
'%filename' => '%filename',
);
$url = strtr($update_url, $variables);
// Download the translation file. Since its contents are volatile,
// cache for only 4 hours.
if ($filename = _make_download_file($url, 3600 * 4)) {
// If this is the core project type, download the translation file
// and place it in every profile and an additional copy in
// modules/system/translations where it can be detected for import
// by other non-default install profiles.
if ($this->type === 'core') {
$profiles = drush_scan_directory($project_directory . '/profiles', '/.*/', array(), 0, FALSE, 'filename', 0, TRUE);
foreach ($profiles as $profile) {
if (is_dir($project_directory . '/profiles/' . $profile->basename)) {
drush_mkdir($project_directory . '/profiles/' . $profile->basename . '/translations');
drush_copy_dir($filename, $project_directory . '/profiles/' . $profile->basename . '/translations/' . $langcode . '.po');
}
}
drush_mkdir($project_directory . '/modules/system/translations');
drush_copy_dir($filename, $project_directory . '/modules/system/translations/' . $langcode . '.po');
}
else {
drush_mkdir($project_directory . '/translations');
drush_copy_dir($filename, $project_directory . '/translations/' . $langcode . '.po', FILE_EXISTS_OVERWRITE);
}
}
else {
$failed[] = $langcode;
}
}
if (empty($failed)) {
drush_log('All translations downloaded for ' . $this->name, LogLevel::OK);
}
else {
drush_log('Unable to download translations for ' . $this->name . ': ' . implode(', ', $failed), LogLevel::WARNING);
}
}
}
return TRUE;
}
/**
* Generate the proper path for this project type.
*
* @param boolean $base
* Whether include the base part (tmp dir). Defaults to TRUE.
*/
protected function generatePath($base = TRUE) {
$path = array();
if ($base) {
$path[] = make_tmp();
$path[] = '__build__';
}
if (!empty($this->contrib_destination)) {
$path[] = $this->contrib_destination;
}
if (!empty($this->subdir)) {
$path[] = $this->subdir;
}
return implode('/', $path);
}
/**
* Return the proper path for dependencies to be placed in.
*
* @return string
* The path that dependencies will be placed in.
*/
protected function buildPath($directory) {
return $this->base_contrib_destination;
}
/**
* Recurse to process additional makefiles that may be found during
* processing.
*/
function recurse($path) {
if (!$this->do_recursion || drush_get_option('no-recursion')) {
drush_log(dt("Preventing recursive makefile parsing for !project",
array("!project" => $this->name)), LogLevel::NOTICE);
return TRUE;
}
$candidates = array(
$this->name . '.make.yml',
$this->name . '.make',
'drupal-org.make.yml',
'drupal-org.make',
);
$makefile = FALSE;
foreach ($candidates as $filename) {
if (file_exists($this->download_location . '/' . $filename)) {
$makefile = $this->download_location . '/' . $filename;
break;
}
}
if (!$makefile) {
return TRUE;
}
drush_log(dt("Found makefile: !makefile", array("!makefile" => basename($makefile))), LogLevel::OK);
// Save the original state of the 'custom' context.
$custom_context = &drush_get_context('custom');
$original_custom_context_values = $custom_context;
$info = make_parse_info_file($makefile, TRUE, $this->options);
if (!($info = make_validate_info_file($info))) {
$result = FALSE;
}
else {
// Inherit the translations specified in the extender makefile.
if (!empty($this->translations)) {
$info['translations'] = $this->translations;
}
// Strip out any modules that have already been processed before this.
foreach ($this->manifest as $name) {
unset($info['projects'][$name]);
}
$build_path = $this->buildPath($this->name);
make_projects(TRUE, trim($build_path, '/'), $info, $this->build_path, $this->download_location);
make_libraries(TRUE, trim($build_path, '/'), $info, $this->build_path, $this->download_location);
$result = TRUE;
}
// Restore original 'custom' context so that any
// settings changes made are used.
$custom_context = $original_custom_context_values;
return $result;
}
}
/**
* For processing Drupal core projects.
*/
class DrushMakeProject_Core extends DrushMakeProject {
/**
* Override constructor for core to adjust project info.
*/
protected function __construct(&$project) {
parent::__construct($project);
// subdir and contrib_destination are not allowed for core.
$this->subdir = '';
$this->contrib_destination = '';
}
/**
* Determine the location to download project to.
*/
function findDownloadLocation() {
$this->path = $this->download_location = $this->generatePath();
$this->project_directory = '';
if (is_dir($this->download_location)) {
return drush_set_error('MAKE_DIRECTORY_EXISTS', dt('Directory not empty: !directory', array('!directory' => $this->download_location)));
}
elseif ($this->download['type'] === 'pm') {
// pm-download will create the final __build__ directory, so nothing to do
// here.
}
else {
drush_mkdir($this->download_location);
}
return $this->download_location;
}
}
/**
* For processing libraries.
*/
class DrushMakeProject_Library extends DrushMakeProject {
/**
* Override constructor for libraries to properly set contrib destination.
*/
protected function __construct(&$project) {
parent::__construct($project);
// Allow libraries to specify where they should live in the build path.
if (isset($project['destination'])) {
$project_path = $project['destination'];
}
else {
$project_path = 'libraries';
}
$this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . $project_path;
}
/**
* No recursion for libraries, sorry :-(
*/
function recurse($path) {
// Return TRUE so that processing continues in the make() method.
return TRUE;
}
/**
* No translations for libraries.
*/
function getTranslations($download_location) {
// Return TRUE so that processing continues in the make() method.
return TRUE;
}
}
/**
* For processing modules.
*/
class DrushMakeProject_Module extends DrushMakeProject {
/**
* Override constructor for modules to properly set contrib destination.
*/
protected function __construct(&$project) {
parent::__construct($project);
$this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'modules';
}
}
/**
* For processing installation profiles.
*/
class DrushMakeProject_Profile extends DrushMakeProject {
/**
* Override contructor for installation profiles to properly set contrib
* destination.
*/
protected function __construct(&$project) {
parent::__construct($project);
$this->contrib_destination = (!empty($this->destination) ? $this->destination : 'profiles');
}
/**
* Find the build path.
*/
protected function buildPath($directory) {
return $this->generatePath(FALSE) . '/' . $directory;
}
}
/**
* For processing themes.
*/
class DrushMakeProject_Theme extends DrushMakeProject {
/**
* Override contructor for themes to properly set contrib destination.
*/
protected function __construct(&$project) {
parent::__construct($project);
$this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'themes';
}
}
/**
* For processing translations.
*/
class DrushMakeProject_Translation extends DrushMakeProject {
/**
* Override constructor for translations to properly set contrib destination.
*/
protected function __construct(&$project) {
parent::__construct($project);
switch ($project['core']) {
case '5.x':
// Don't think there's an automatic place we can put 5.x translations,
// so we'll toss them in a translations directory in the Drupal root.
$this->contrib_destination = ($this->base_contrib_destination != '.' ? $this->base_contrib_destination . '/' : '') . 'translations';
break;
default:
$this->contrib_destination = '';
break;
}
}
}

View file

@ -0,0 +1,698 @@
<?php
/**
* @file
* General utility functions for Drush Make.
*/
use Drush\Log\LogLevel;
use Drush\Make\Parser\ParserIni;
use Drush\Make\Parser\ParserYaml;
/**
* Helper function to parse a makefile and prune projects.
*/
function make_parse_info_file($makefile) {
$info = _make_parse_info_file($makefile);
// Support making just a portion of a make file.
$include_only = array(
'projects' => array_filter(drush_get_option_list('projects')),
'libraries' => array_filter(drush_get_option_list('libraries')),
);
$info = make_prune_info_file($info, $include_only);
if ($info === FALSE || ($info = make_validate_info_file($info)) === FALSE) {
return FALSE;
}
return $info;
}
/**
* Parse makefile recursively.
*/
function _make_parse_info_file($makefile, $element = 'includes') {
if (!($data = make_get_data($makefile))) {
return drush_set_error('MAKE_INVALID_MAKE_FILE', dt('Invalid or empty make file: !makefile', array('!makefile' => $makefile)));
}
// $info['format'] will specify the determined format.
$info = _make_determine_format($data);
// Set any allowed options.
if (!empty($info['options'])) {
foreach ($info['options'] as $key => $value) {
if (_make_is_override_allowed($key)) {
// n.b. 'custom' context has lower priority than 'cli', so
// options entered on the command line will "mask" makefile options.
drush_set_option($key, $value, 'custom');
}
}
}
// Include any makefiles specified on the command line.
if ($include_makefiles = drush_get_option_list('includes', FALSE)) {
drush_unset_option('includes'); // Avoid infinite loop.
$info['includes'] = is_array($info['includes']) ? $info['includes'] : array();
foreach ($include_makefiles as $include_make) {
if (!array_search($include_make, $info['includes'])) {
$info['includes'][] = $include_make;
}
}
}
// Override elements with values from makefiles specified on the command line.
if ($overrides = drush_get_option_list('overrides', FALSE)) {
drush_unset_option('overrides'); // Avoid infinite loop.
$info['overrides'] = is_array($info['overrides']) ? $info['overrides'] : array();
foreach ($overrides as $override) {
if (!array_search($override, $info['overrides'])) {
$info['overrides'][] = $override;
}
}
}
$info = _make_merge_includes_recursively($info, $makefile);
$info = _make_merge_includes_recursively($info, $makefile, 'overrides');
return $info;
}
/**
* Helper function to merge includes recursively.
*/
function _make_merge_includes_recursively($info, $makefile, $element = 'includes') {
if (!empty($info[$element])) {
if (is_array($info[$element])) {
$includes = array();
foreach ($info[$element] as $key => $include) {
if (!empty($include)) {
if (!$include_makefile = _make_get_include_path($include, $makefile)) {
return make_error('BUILD_ERROR', dt("Cannot determine include file location: !include", array('!include' => $include)));
}
if ($element == 'overrides') {
$info = array_replace_recursive($info, _make_parse_info_file($include_makefile, $element));
}
else {
$info = array_replace_recursive(_make_parse_info_file($include_makefile), $info);
}
unset($info[$element][$key]);
// Move core back to the top of the list, where
// make_generate_from_makefile() expects it.
if (!empty($info['projects'])) {
array_reverse($info['projects']);
}
}
}
}
}
// Ensure $info['projects'] is an associative array, so that we can merge
// includes properly.
make_normalize_info($info);
return $info;
}
/**
* Helper function to determine the proper path for an include makefile.
*/
function _make_get_include_path($include, $makefile) {
if (is_array($include) && $include['download']['type'] = 'git') {
$tmp_dir = make_tmp();
make_download_git($include['makefile'], $include['download']['type'], $include['download'], $tmp_dir);
$include_makefile = $tmp_dir . '/' . $include['makefile'];
}
elseif (is_string($include)) {
$include_path = dirname($makefile);
if (make_valid_url($include, TRUE)) {
$include_makefile = $include;
}
elseif (file_exists($include_path . '/' . $include)) {
$include_makefile = $include_path . '/' . $include;
}
elseif (file_exists($include)) {
$include_makefile = $include;
}
else {
return make_error('BUILD_ERROR', dt("Include file missing: !include", array('!include' => $include)));
}
}
else {
return FALSE;
}
return $include_makefile;
}
/**
* Expand shorthand elements, so that we have an associative array.
*/
function make_normalize_info(&$info) {
if (isset($info['projects'])) {
foreach($info['projects'] as $key => $project) {
if (is_numeric($key) && is_string($project)) {
unset($info['projects'][$key]);
$info['projects'][$project] = array(
'version' => '',
);
}
if (is_string($key) && is_numeric($project)) {
$info['projects'][$key] = array(
'version' => $project,
);
}
}
}
}
/**
* Remove entries in the info file in accordance with the options passed in.
* Entries are either explicitly 'allowed' (with the $include_only parameter) in
* which case all *other* entries will be excluded.
*
* @param array $info
* A parsed info file.
*
* @param array $include_only
* (Optional) Array keyed by entry type (e.g. 'libraries') against an array of
* allowed keys for that type. The special value '*' means 'all entries of
* this type'. If this parameter is omitted, no entries will be excluded.
*
* @return array
* The $info array, pruned if necessary.
*/
function make_prune_info_file($info, $include_only = array()) {
// We may get passed FALSE in some cases.
// Also we cannot prune an empty array, so no point in this code running!
if (empty($info)) {
return $info;
}
// We will accrue an explanation of our activities here.
$msg = array();
$msg['scope'] = dt("Drush make restricted to the following entries:");
$pruned = FALSE;
if (count(array_filter($include_only))) {
$pruned = TRUE;
foreach ($include_only as $type => $keys) {
if (!isset($info[$type])) {
continue;
}
// For translating
// dt("Projects");
// dt("Libraries");
$type_title = dt(ucfirst($type));
// Handle the special '*' value.
if (in_array('*', $keys)) {
$msg[$type] = dt("!entry_type: <All>", array('!entry_type' => $type_title));
}
// Handle a (possibly empty) array of keys to include/exclude.
else {
$info[$type] = array_intersect_key($info[$type], array_fill_keys($keys, 1));
unset($msg[$type]);
if (!empty($info[$type])) {
$msg[$type] = dt("!entry_type: !make_entries", array('!entry_type' => $type_title, '!make_entries' => implode(', ', array_keys($info[$type]))));
}
}
}
}
if ($pruned) {
// Make it clear to the user what's going on.
drush_log(implode("\n", $msg), LogLevel::OK);
// Throw an error if these restrictions reduced the make to nothing.
if (empty($info['projects']) && empty($info['libraries'])) {
// This error mentions the options explicitly to make it as clear as
// possible to the user why this error has occurred.
make_error('BUILD_ERROR', dt("All projects and libraries have been excluded. Review the 'projects' and 'libraries' options."));
}
}
return $info;
}
/**
* Validate the make file.
*/
function make_validate_info_file($info) {
// Assume no errors to start.
$errors = FALSE;
if (empty($info['core'])) {
make_error('BUILD_ERROR', dt("The 'core' attribute is required"));
$errors = TRUE;
}
// Standardize on core.
elseif (preg_match('/^(\d+)(\.(x|(\d+)(-[a-z0-9]+)?))?$/', $info['core'], $matches)) {
// An exact version of core has been specified, so pass that to an
// internal variable for storage.
if (isset($matches[4])) {
$info['core_release'] = $info['core'];
}
// Format the core attribute consistently.
$info['core'] = $matches[1] . '.x';
}
else {
make_error('BUILD_ERROR', dt("The 'core' attribute !core has an incorrect format.", array('!core' => $info['core'])));
$errors = TRUE;
}
if (!isset($info['api'])) {
$info['api'] = MAKE_API;
drush_log(dt("You need to specify an API version of two in your makefile:\napi = !api", array("!api" => MAKE_API)), LogLevel::WARNING);
}
elseif ($info['api'] != MAKE_API) {
make_error('BUILD_ERROR', dt("The specified API attribute is incompatible with this version of Drush Make."));
$errors = TRUE;
}
$names = array();
// Process projects.
if (isset($info['projects'])) {
if (!is_array($info['projects'])) {
make_error('BUILD_ERROR', dt("'projects' attribute must be an array."));
$errors = TRUE;
}
else {
// Filter out entries that have been forcibly removed via [foo] = FALSE.
$info['projects'] = array_filter($info['projects']);
foreach ($info['projects'] as $project => $project_data) {
// Project has an attributes array.
if (is_string($project) && is_array($project_data)) {
if (in_array($project, $names)) {
make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project)));
$errors = TRUE;
}
$names[] = $project;
foreach ($project_data as $attribute => $value) {
// Prevent malicious attempts to access other areas of the
// filesystem.
if (in_array($attribute, array('subdir', 'directory_name', 'contrib_destination')) && !make_safe_path($value)) {
$args = array(
'!path' => $value,
'!attribute' => $attribute,
'!project' => $project,
);
make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in project !project.", $args));
$errors = TRUE;
}
}
}
// Cover if there is no project info, it's just a project name.
elseif (is_numeric($project) && is_string($project_data)) {
if (in_array($project_data, $names)) {
make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project_data)));
$errors = TRUE;
}
$names[] = $project_data;
unset($info['projects'][$project]);
$info['projects'][$project_data] = array();
}
// Convert shorthand project version style to array format.
elseif (is_string($project_data)) {
if (in_array($project, $names)) {
make_error('BUILD_ERROR', dt("Project !project defined twice (remove the first projects[] = !project).", array('!project' => $project)));
$errors = TRUE;
}
$names[] = $project;
$info['projects'][$project] = array('version' => $project_data);
}
else {
make_error('BUILD_ERROR', dt('Project !project incorrectly specified.', array('!project' => $project)));
$errors = TRUE;
}
}
}
}
if (isset($info['libraries'])) {
if (!is_array($info['libraries'])) {
make_error('BUILD_ERROR', dt("'libraries' attribute must be an array."));
$errors = TRUE;
}
else {
// Filter out entries that have been forcibly removed via [foo] = FALSE.
$info['libraries'] = array_filter($info['libraries']);
foreach ($info['libraries'] as $library => $library_data) {
if (is_array($library_data)) {
foreach ($library_data as $attribute => $value) {
// Unset disallowed attributes.
if (in_array($attribute, array('contrib_destination'))) {
unset($info['libraries'][$library][$attribute]);
}
// Prevent malicious attempts to access other areas of the
// filesystem.
elseif (in_array($attribute, array('contrib_destination', 'directory_name')) && !make_safe_path($value)) {
$args = array(
'!path' => $value,
'!attribute' => $attribute,
'!library' => $library,
);
make_error('BUILD_ERROR', dt("Illegal path !path for '!attribute' attribute in library !library.", $args));
$errors = TRUE;
}
}
}
}
}
}
// Convert shorthand project/library download style to array format.
foreach (array('projects', 'libraries') as $type) {
if (isset($info[$type]) && is_array($info[$type])) {
foreach ($info[$type] as $name => $item) {
if (!empty($item['download']) && is_string($item['download'])) {
$info[$type][$name]['download'] = array('url' => $item['download']);
}
}
}
}
// Apply defaults after projects[] array has been expanded, but prior to
// external validation.
make_apply_defaults($info);
foreach (drush_command_implements('make_validate_info') as $module) {
$function = $module . '_make_validate_info';
$return = $function($info);
if ($return) {
$info = $return;
}
else {
$errors = TRUE;
}
}
if ($errors) {
return FALSE;
}
return $info;
}
/**
* Verify the syntax of the given URL.
*
* Copied verbatim from includes/common.inc
*
* @see valid_url
*/
function make_valid_url($url, $absolute = FALSE) {
if ($absolute) {
return (bool) preg_match("
/^ # Start at the beginning of the text
(?:ftp|https?):\/\/ # Look for ftp, http, or https schemes
(?: # Userinfo (optional) which is typically
(?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
(?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
)?
(?:
(?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
|(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
)
(?::[0-9]+)? # Server port number (optional)
(?:[\/|\?]
(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
*)?
$/xi", $url);
}
else {
return (bool) preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
}
}
/**
* Find, and possibly create, a temporary directory.
*
* @param boolean $set
* Must be TRUE to create a directory.
* @param string $directory
* Pass in a directory to use. This is required if using any
* concurrent operations.
*
* @todo Merge with drush_tempdir().
*/
function make_tmp($set = TRUE, $directory = NULL) {
static $tmp_dir;
if (isset($directory) && !isset($tmp_dir)) {
$tmp_dir = $directory;
}
if (!isset($tmp_dir) && $set) {
$tmp_dir = drush_find_tmp();
if (strrpos($tmp_dir, '/') == strlen($tmp_dir) - 1) {
$tmp_dir .= 'make_tmp_' . time() . '_' . uniqid();
}
else {
$tmp_dir .= '/make_tmp_' . time() . '_' . uniqid();
}
if (!drush_get_option('no-clean', FALSE)) {
drush_register_file_for_deletion($tmp_dir);
}
if (file_exists($tmp_dir)) {
return make_tmp(TRUE);
}
// Create the directory.
drush_mkdir($tmp_dir);
}
return $tmp_dir;
}
/**
* Removes the temporary build directory. On failed builds, this is handled by
* drush_register_file_for_deletion().
*/
function make_clean_tmp() {
if (!($tmp_dir = make_tmp(FALSE))) {
return;
}
if (!drush_get_option('no-clean', FALSE)) {
drush_delete_dir($tmp_dir);
}
else {
drush_log(dt('Temporary directory: !dir', array('!dir' => $tmp_dir)), LogLevel::OK);
}
}
/**
* Prepare a Drupal installation, copying default.settings.php to settings.php.
*/
function make_prepare_install($build_path) {
$default = make_tmp() . '/__build__/sites/default';
drush_copy_dir($default . DIRECTORY_SEPARATOR . 'default.settings.php', $default . DIRECTORY_SEPARATOR . 'settings.php', FILE_EXISTS_OVERWRITE);
drush_mkdir($default . '/files');
chmod($default . DIRECTORY_SEPARATOR . 'settings.php', 0666);
chmod($default . DIRECTORY_SEPARATOR . 'files', 0777);
}
/**
* Calculate a cksum on each file in the build, and md5 the resulting hashes.
*/
function make_md5() {
return drush_dir_md5(make_tmp());
}
/**
* @todo drush_archive_dump() also makes a tar. Consolidate?
*/
function make_tar($build_path) {
$tmp_path = make_tmp();
drush_mkdir(dirname($build_path));
$filename = basename($build_path);
$dirname = basename($build_path, '.tar.gz');
// Move the build directory to a more human-friendly name, so that tar will
// use it instead.
drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . '__build__', $tmp_path . DIRECTORY_SEPARATOR . $dirname, TRUE);
// Only move the tar file to it's final location if it's been built
// successfully.
if (drush_shell_exec("%s -C %s -Pczf %s %s", drush_get_tar_executable(), $tmp_path, $tmp_path . '/' . $filename, $dirname)) {
drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $filename, $build_path, TRUE);
};
// Move the build directory back to it's original location for consistency.
drush_move_dir($tmp_path . DIRECTORY_SEPARATOR . $dirname, $tmp_path . DIRECTORY_SEPARATOR . '__build__');
}
/**
* Logs an error unless the --force-complete command line option is specified.
*/
function make_error($error_code, $message) {
if (drush_get_option('force-complete')) {
drush_log("$error_code: $message -- build forced", LogLevel::WARNING);
}
else {
return drush_set_error($error_code, $message);
}
}
/**
* Checks an attribute's path to ensure it's not maliciously crafted.
*
* @param string $path
* The path to check.
*/
function make_safe_path($path) {
return !preg_match("+^/|^\.\.|/\.\./+", $path);
}
/**
* Get data based on the source.
*
* This is a helper function to abstract the retrieval of data, so that it can
* come from files, STDIN, etc. Currently supports filepath and STDIN.
*
* @param string $data_source
* The path to a file, or '-' for STDIN.
*
* @return string
* The raw data as a string.
*/
function make_get_data($data_source) {
if ($data_source == '-') {
// See http://drupal.org/node/499758 before changing this.
$stdin = fopen('php://stdin', 'r');
$data = '';
$has_input = FALSE;
while ($line = fgets($stdin)) {
$has_input = TRUE;
$data .= $line;
}
if ($has_input) {
return $data;
}
return FALSE;
}
// Local file.
elseif (!strpos($data_source, '://')) {
$data = file_get_contents($data_source);
}
// Remote file.
else {
$file = _make_download_file($data_source);
$data = file_get_contents($file);
drush_op('unlink', $file);
}
return $data;
}
/**
* Apply any defaults.
*
* @param array &$info
* A parsed make array.
*/
function make_apply_defaults(&$info) {
if (isset($info['defaults'])) {
$defaults = $info['defaults'];
foreach ($defaults as $type => $default_data) {
if (isset($info[$type])) {
foreach ($info[$type] as $project => $data) {
$info[$type][$project] = _drush_array_overlay_recursive($default_data, $info[$type][$project]);
}
}
else {
drush_log(dt("Unknown attribute '@type' in defaults array", array('@type' => $type)), LogLevel::WARNING);
}
}
}
}
/**
* Check if makefile overrides are allowed
*
* @param array $option
* The option to check.
*/
function _make_is_override_allowed ($option) {
$allow_override = drush_get_option('allow-override', 'all');
if ($allow_override == 'all') {
$allow_override = array();
}
elseif (!is_array($allow_override)) {
$allow_override = _convert_csv_to_array($allow_override);
}
if ((empty($allow_override)) || ((in_array($option, $allow_override)) && (!in_array('none', $allow_override)))) {
return TRUE;
}
drush_log(dt("'!option' not allowed; use --allow-override=!option or --allow-override=all to permit", array("!option" => $option)), LogLevel::WARNING);
return FALSE;
}
/**
* Gather any working copy options.
*
* @param array $download
* The download array.
*/
function _get_working_copy_option($download) {
$wc = '';
if (_make_is_override_allowed('working-copy') && isset ($download['working-copy'])) {
$wc = $download['working-copy'];
}
else {
$wc = drush_get_option('working-copy');
}
return $wc;
}
/**
* Given data from stdin, determine format.
*
* @return array|bool
* Returns parsed data if it matches any known format.
*/
function _make_determine_format($data) {
// Most .make files will have a `core` attribute. Use this to determine
// the format.
if (preg_match('/^\s*core:/m', $data)) {
$parsed = ParserYaml::parse($data);
$parsed['format'] = 'yaml';
return $parsed;
}
elseif (preg_match('/^\s*core\s*=/m', $data)) {
$parsed = ParserIni::parse($data);
$parsed['format'] = 'ini';
return $parsed;
}
// If the .make file did not have a core attribute, it is being included
// by another .make file. Test YAML first to avoid segmentation faults from
// preg_match in INI parser.
$yaml_parse_exception = FALSE;
try {
if ($parsed = ParserYaml::parse($data)) {
$parsed['format'] = 'yaml';
return $parsed;
}
}
catch (\Symfony\Component\Yaml\Exception\ParseException $e) {
// Note that an exception was thrown, and display after .ini parsing.
$yaml_parse_exception = $e;
}
// Try INI format.
if ($parsed = ParserIni::parse($data)) {
$parsed['format'] = 'ini';
return $parsed;
}
if ($yaml_parse_exception) {
throw $e;
}
return drush_set_error('MAKE_STDIN_ERROR', dt('Unknown make file format'));
}

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* make-update command implementation.
*/
/**
* Command callback for make-update.
*/
function drush_make_update($makefile = NULL) {
// Process makefile and get projects array.
$info = _make_parse_info_file($makefile);
make_prepare_projects(FALSE, $info);
$make_projects = drush_get_option('DRUSH_MAKE_PROJECTS', FALSE);
// Pick projects coming from drupal.org and adjust its structure
// to feed update_status engine.
// We provide here some heuristics to determine if a git clone comes
// from drupal.org and also guess its version.
// #TODO# move git checks to make_prepare_projects() and use it to leverage
// git_drupalorg engine.
$projects = array();
foreach ($make_projects as $project_name => $project) {
if (($project['download']['type'] == 'git') && !empty($project['download']['url'])) {
// TODO check that tag or branch are valid version strings (with pm_parse_version()).
if (!empty($project['download']['tag'])) {
$version = $project['download']['tag'];
}
elseif (!empty($project['download']['branch'])) {
$version = $project['download']['branch'] . '-dev';
}
/*
elseif (!empty($project['download']['refspec'])) {
#TODO# Parse refspec.
}
*/
else {
// If no tag or branch, we can't match a d.o version.
continue;
}
$projects[$project_name] = $project + array(
'path' => '',
'label' => $project_name,
'version' => $version,
);
}
elseif ($project['download']['type'] == 'pm') {
$projects[$project_name] = $project + array(
'path' => '',
'label' => $project_name,
);
}
}
// Check for updates.
$update_status = drush_get_engine('update_status');
$update_info = $update_status->getStatus($projects, TRUE);
$security_only = drush_get_option('security-only', FALSE);
foreach ($update_info as $project_name => $project_update_info) {
if (!$security_only || ($security_only && $project_update_info['status'] == DRUSH_UPDATESTATUS_NOT_SECURE)) {
$make_projects[$project_name]['download']['full_version'] = $project_update_info['recommended'];
}
}
// Inject back make projects and generate the updated makefile.
drush_set_option('DRUSH_MAKE_PROJECTS', $make_projects);
make_generate_from_makefile(drush_get_option('result-file'), $makefile);
}

View file

@ -0,0 +1,394 @@
<?php
/**
* @file
* pm-download command implementation.
*/
use Drush\Log\LogLevel;
use Drush\UpdateService\ReleaseInfo;
/**
* Implements drush_hook_COMMAND_validate().
*/
function drush_pm_download_validate() {
// Accomodate --select to the values accepted by release_info.
$select = drush_get_option('select', 'auto');
if ($select === TRUE) {
drush_set_option('select', 'always');
}
else if ($select === FALSE) {
drush_set_option('select', 'never');
}
// Validate the user specified destination directory.
$destination = drush_get_option('destination');
if (!empty($destination)) {
$destination = rtrim($destination, DIRECTORY_SEPARATOR);
if (!is_dir($destination)) {
drush_print(dt("The directory !destination does not exist.", array('!destination' => $destination)));
if (!drush_get_context('DRUSH_SIMULATE')) {
if (drush_confirm(dt('Would you like to create it?'))) {
drush_mkdir($destination, TRUE);
}
if (!is_dir($destination)) {
return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Unable to create destination directory !destination.', array('!destination' => $destination)));
}
}
}
if (!is_writable($destination)) {
return drush_set_error('DRUSH_PM_NO_DESTINATION', dt('Destination directory !destination is not writable.', array('!destination' => $destination)));
}
// Ignore --use-site-dir, if given.
if (drush_get_option('use-site-dir', FALSE)) {
drush_set_option('use-site-dir', FALSE);
}
}
// Validate --variant or enforce a sane default.
$variant = drush_get_option('variant', FALSE);
if ($variant) {
$variants = array('full', 'projects', 'profile-only');
if (!in_array($variant, $variants)) {
return drush_set_error('DRUSH_PM_PROFILE_INVALID_VARIANT', dt('Invalid variant !variant. Valid values: !variants.', array('!variant' => $variant, '!variants' => implode(', ', $variants))));
}
}
// 'full' and 'projects' variants are only valid for wget package handler.
$package_handler = drush_get_option('package-handler', 'wget');
if (($package_handler != 'wget') && ($variant != 'profile-only')) {
$new_variant = 'profile-only';
if ($variant) {
drush_log(dt('Variant !variant is incompatible with !ph package-handler.', array('!variant' => $variant, '!ph' => $package_handler)), LogLevel::WARNING);
}
}
// If we are working on a drupal root, full variant is not an option.
else if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
if ((!$variant) || (($variant == 'full') && (!isset($new_variant)))) {
$new_variant = 'projects';
}
if ($variant == 'full') {
drush_log(dt('Variant full is not a valid option within a Drupal root.'), LogLevel::WARNING);
}
}
if (isset($new_variant)) {
drush_set_option('variant', $new_variant);
if ($variant) {
drush_log(dt('Switching to --variant=!variant.', array('!variant' => $new_variant)), LogLevel::OK);
}
}
}
/**
* Command callback. Download Drupal core or any project.
*/
function drush_pm_download() {
$release_info = drush_get_engine('release_info');
if (!$requests = pm_parse_arguments(func_get_args(), FALSE)) {
$requests = array('drupal');
}
// Pick cli options.
$status_url = drush_get_option('source', ReleaseInfo::DEFAULT_URL);
$restrict_to = drush_get_option('dev', '');
$select = drush_get_option('select', 'auto');
$all = drush_get_option('all', FALSE);
// If we've bootstrapped a Drupal site and the user may have the chance
// to select from a list of filtered releases, we want to pass
// the installed project version, if any.
$projects = array();
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
if (!$all and in_array($select, array('auto', 'always'))) {
$projects = drush_get_projects();
}
}
// Get release history for each request and download the project.
foreach ($requests as $request) {
$request = pm_parse_request($request, $status_url, $projects);
$version = isset($projects[$request['name']]) ? $projects[$request['name']]['version'] : NULL;
$release = $release_info->selectReleaseBasedOnStrategy($request, $restrict_to, $select, $all, $version);
if ($release == FALSE) {
// Stop working on the first failure. Return silently on user abort.
if (drush_get_context('DRUSH_USER_ABORT', FALSE)) {
return FALSE;
}
// Signal that the command failed for all other problems.
return drush_set_error('DRUSH_DOWNLOAD_FAILED', dt("Could not download requested project(s)."));
}
$request['version'] = $release['version'];
$project_release_info = $release_info->get($request);
$request['project_type'] = $project_release_info->getType();
// Determine the name of the directory that will contain the project.
// We face here all the assymetries to make it smooth for package handlers.
// For Drupal core: --drupal-project-rename or drupal-x.y
if (($request['project_type'] == 'core') ||
(($request['project_type'] == 'profile') && (drush_get_option('variant', 'full') == 'full'))) {
// Avoid downloading core into existing core.
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_ROOT) {
if (strpos(realpath(drush_get_option('destination')), DRUPAL_ROOT) !== FALSE) {
return drush_set_error('DRUSH_PM_DOWNLOAD_TRANSLATIONS_FORBIDDEN', dt('It\'s forbidden to download !project core into an existing core.', array('!project' => $request['name'])));
}
}
if ($rename = drush_get_option('drupal-project-rename', FALSE)) {
if ($rename === TRUE) {
$request['project_dir'] = $request['name'];
}
else {
$request['project_dir'] = $rename;
}
}
else {
// Set to drupal-x.y, the expected name for .tar.gz contents.
// Explicitly needed for cvs package handler.
$request['project_dir'] = strtolower(strtr($release['name'], ' ', '-'));
}
}
// For the other project types we want the project name. Including core
// variant for profiles. Note those come with drupal-x.y in the .tar.gz.
else {
$request['project_dir'] = $request['name'];
}
// Download the project to a temporary location.
drush_log(dt('Downloading project !name ...', array('!name' => $request['name'])));
$request['full_project_path'] = package_handler_download_project($request, $release);
if (!$request['full_project_path']) {
// Delete the cached update service file since it may be invalid.
$release_info->clearCached($request);
drush_log(dt('Error downloading !name', array('!name' => $request['name']), LogLevel::ERROR));
continue;
}
// Determine the install location for the project. User provided
// --destination has preference.
$destination = drush_get_option('destination');
if (!empty($destination)) {
if (!file_exists($destination)) {
drush_mkdir($destination);
}
$request['project_install_location'] = realpath($destination);
}
else {
$request['project_install_location'] = _pm_download_destination($request['project_type']);
}
// If user did not provide --destination, then call the
// download-destination-alter hook to give the chance to any commandfiles
// to adjust the install location or abort it.
if (empty($destination)) {
$result = drush_command_invoke_all_ref('drush_pm_download_destination_alter', $request, $release);
if (array_search(FALSE, $result, TRUE) !== FALSE) {
return FALSE;
}
}
// Load version control engine and detect if (the parent directory of) the
// project install location is under a vcs.
if (!$version_control = drush_pm_include_version_control($request['project_install_location'])) {
continue;
}
$request['project_install_location'] .= '/' . $request['project_dir'];
if ($version_control->engine == 'backup') {
// Check if install location already exists.
if (is_dir($request['project_install_location'])) {
if (drush_confirm(dt('Install location !location already exists. Do you want to overwrite it?', array('!location' => $request['project_install_location'])))) {
drush_delete_dir($request['project_install_location'], TRUE);
}
else {
drush_log(dt("Skip installation of !project to !dest.", array('!project' => $request['name'], '!dest' => $request['project_install_location'])), LogLevel::WARNING);
continue;
}
}
}
else {
// Find and unlink all files but the ones in the vcs control directories.
$skip_list = array('.', '..');
$skip_list = array_merge($skip_list, drush_version_control_reserved_files());
drush_scan_directory($request['project_install_location'], '/.*/', $skip_list, 'unlink', TRUE, 'filename', 0, TRUE);
}
// Copy the project to the install location.
if (drush_op('_drush_recursive_copy', $request['full_project_path'], $request['project_install_location'])) {
drush_log(dt("Project !project (!version) downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])), LogLevel::SUCCESS);
// Adjust full_project_path to the final project location.
$request['full_project_path'] = $request['project_install_location'];
// If the version control engine is a proper vcs we also need to remove
// orphan directories.
if ($version_control->engine != 'backup') {
$empty_dirs = drush_find_empty_directories($request['full_project_path'], $version_control->reserved_files());
foreach ($empty_dirs as $empty_dir) {
// Some VCS files are read-only on Windows (e.g., .svn/entries).
drush_delete_dir($empty_dir, TRUE);
}
}
// Post download actions.
package_handler_post_download($request, $release);
drush_command_invoke_all('drush_pm_post_download', $request, $release);
$version_control->post_download($request);
// Print release notes if --notes option is set.
if (drush_get_option('notes') && !drush_get_context('DRUSH_PIPE')) {
$project_release_info->getReleaseNotes($release['version'], FALSE);
}
// Inform the user about available modules a/o themes in the downloaded project.
drush_pm_extensions_in_project($request);
}
else {
// We don't `return` here in order to proceed with downloading additional projects.
drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt("Project !project (!version) could not be downloaded to !dest.", array('!project' => $request['name'], '!version' => $release['version'], '!dest' => $request['project_install_location'])));
}
// Notify about this project.
if (drush_notify_allowed('pm-download')) {
$msg = dt('Project !project (!version) downloaded to !install.', array(
'!project' => $name,
'!version' => $release['version'],
'!install' => $request['project_install_location'],
));
drush_notify_send(drush_notify_command_message('pm-download', $msg));
}
}
}
/**
* Implementation of hook_drush_pm_download_destination_alter().
*
* Built-in download-destination-alter hook. This particular version of
* the hook will move modules that contain only Drush commands to
* /usr/share/drush/commands if it exists, or $HOME/.drush if the
* site-wide location does not exist.
*/
function pm_drush_pm_download_destination_alter(&$request, $release) {
// A module is a pure Drush command if it has no .info.yml (8+) and contains no
// .drush.inc files. Skip this test for Drush itself, though; we do
// not want to download Drush to the ~/.drush folder.
if (in_array($request['project_type'], array('module', 'utility')) && ($request['name'] != 'drush')) {
$drush_command_files = drush_scan_directory($request['full_project_path'], '/.*\.drush.inc/');
if (!empty($drush_command_files)) {
$pattern = drush_drupal_major_version() >= 8 ? '/.*\.info/' : '/.*\.module/';
$module_files = drush_scan_directory($request['full_project_path'], $pattern);
if (empty($module_files)) {
$install_dir = drush_get_context('DRUSH_SITE_WIDE_COMMANDFILES');
if (!is_dir($install_dir) || !is_writable($install_dir)) {
$install_dir = drush_get_context('DRUSH_PER_USER_CONFIGURATION');
}
// Make the .drush dir if it does not already exist.
if (!is_dir($install_dir)) {
drush_mkdir($install_dir, FALSE);
}
// Change the location if the mkdir worked.
if (is_dir($install_dir)) {
$request['project_install_location'] = $install_dir;
}
}
// We need to clear the Drush commandfile cache so that
// our newly-downloaded Drush extension commandfiles can be found.
drush_cache_clear_all();
}
}
}
/**
* Determines a candidate destination directory for a particular site path.
*
* Optionally attempts to create the directory.
*
* @return String the candidate destination if it exists.
*/
function _pm_download_destination_lookup($type, $drupal_root, $sitepath, $create = FALSE) {
// Profiles in Drupal < 8
if (($type == 'profile') && (drush_drupal_major_version() < 8)) {
$destination = 'profiles';
}
// Type: module, theme or profile.
else {
if ($type == 'theme engine') {
$destination = 'themes/engines';
} else {
$destination = $type . 's';
}
// Prefer /contrib if it exists.
if ($sitepath) {
$destination = $sitepath . '/' . $destination;
}
$contrib = $destination . '/contrib';
if (is_dir($contrib)) {
$destination = $contrib;
}
}
if ($create) {
drush_log(dt('Attempting to create destination directory at !dir', array('!dir' => $destination)));
drush_mkdir($destination, TRUE);
}
if (is_dir($destination)) {
drush_log(dt('Using destination directory !dir', array('!dir' => $destination)));
return $destination;
}
drush_log(dt('Could not find destination directory at !dir', array('!dir' => $destination)));
return FALSE;
}
/**
* Returns the best destination for a particular download type we can find.
*
* It is based on the project type and drupal and site contexts.
*/
function _pm_download_destination($type) {
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
$site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT');
$full_site_root = (empty($drupal_root) || empty($site_root)) ? '' : $drupal_root .'/'. $site_root;
$sitewide = empty($drupal_root) ? '' : $drupal_root . '/' . drush_drupal_sitewide_directory();
$in_site_directory = FALSE;
// Check if we are running within the site directory.
if (strpos(realpath(drush_cwd()), realpath($full_site_root)) !== FALSE || (drush_get_option('use-site-dir', FALSE))) {
$in_site_directory = TRUE;
}
$destination = '';
if ($type != 'core') {
// Attempt 1: If we are in a specific site directory, and the destination
// directory already exists, then we use that.
if (empty($destination) && $site_root && $in_site_directory) {
$create_dir = drush_get_option('use-site-dir', FALSE);
$destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, $create_dir);
}
// Attempt 2: If the destination directory already exists for
// the sitewide directory, use that.
if (empty($destination) && $drupal_root) {
$destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide);
}
// Attempt 3: If a specific (non default) site directory exists and
// the sitewide directory does not exist, then create destination
// in the site specific directory.
if (empty($destination) && $site_root && $site_root !== 'sites/default' && is_dir($full_site_root) && !is_dir($sitewide)) {
$destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
}
// Attempt 4: If sitewide directory exists, then create destination there.
if (empty($destination) && is_dir($sitewide)) {
$destination = _pm_download_destination_lookup($type, $drupal_root, $sitewide, TRUE);
}
// Attempt 5: If site directory exists (even default), then create
// destination in that directory.
if (empty($destination) && $site_root && is_dir($full_site_root)) {
$destination = _pm_download_destination_lookup($type, $drupal_root, $full_site_root, TRUE);
}
}
// Attempt 6: If we didn't find a valid directory yet (or we somehow found
// one that doesn't exist) we always fall back to the current directory.
if (empty($destination) || !is_dir($destination)) {
$destination = drush_cwd();
}
return $destination;
}

View file

@ -0,0 +1,152 @@
<?php
/**
* @file
* pm-info command implementation.
*/
use Drush\Log\LogLevel;
/**
* Command callback. Show detailed info for one or more extensions.
*/
function drush_pm_info() {
$result = array();
$args = pm_parse_arguments(func_get_args());
$extension_info = drush_get_extensions(FALSE);
_drush_pm_expand_extensions($args, $extension_info);
// If no extensions are provided, show all.
if (count($args) == 0) {
$args = array_keys($extension_info);
}
foreach ($args as $extension) {
if (isset($extension_info[$extension])) {
$info = $extension_info[$extension];
}
else {
drush_log(dt('!extension was not found.', array('!extension' => $extension)), LogLevel::WARNING);
continue;
}
if (drush_extension_get_type($info) == 'module') {
$data = _drush_pm_info_module($info);
}
else {
$data = _drush_pm_info_theme($info);
}
$result[$extension] = $data;
}
return $result;
}
/**
* Output format formatter-filter callback.
*
* @see drush_parse_command()
* @see drush_outputformat
*/
function _drush_pm_info_format_table_data($data) {
$result = array();
foreach ($data as $extension => $info) {
foreach($info as $key => $value) {
if (is_array($value)) {
if (empty($value)) {
$value = 'none';
}
else {
$value = implode(', ', $value);
}
}
$result[$extension][$key] = $value;
}
}
return $result;
}
/**
* Return an array with general info of an extension.
*/
function _drush_pm_info_extension($info) {
$data['extension'] = drush_extension_get_name($info);
$data['project'] = isset($info->info['project'])?$info->info['project']:dt('Unknown');
$data['type'] = drush_extension_get_type($info);
$data['title'] = $info->info['name'];
$data['config'] = isset($info->info['configure']) ? $info->info['configure'] : dt('None');
$data['description'] = $info->info['description'];
$data['version'] = $info->info['version'];
$data['date'] = isset($info->info['datestamp']) ? format_date($info->info['datestamp'], 'custom', 'Y-m-d') : NULL;
$data['package'] = $info->info['package'];
$data['core'] = $info->info['core'];
$data['php'] = $info->info['php'];
$data['status'] = drush_get_extension_status($info);
$data['path'] = drush_extension_get_path($info);
return $data;
}
/**
* Return an array with info of a module.
*/
function _drush_pm_info_module($info) {
$major_version = drush_drupal_major_version();
$data = _drush_pm_info_extension($info);
if ($info->schema_version > 0) {
$schema_version = $info->schema_version;
}
elseif ($info->schema_version == -1) {
$schema_version = "no schema installed";
}
else {
$schema_version = "module has no schema";
}
$data['schema_version'] = $schema_version;
if ($major_version == 7) {
$data['files'] = $info->info['files'];
}
$data['requires'] = $info->info['dependencies'];
if ($major_version == 6) {
$requiredby = $info->info['dependents'];
}
else {
$requiredby = array_keys($info->required_by);
}
$data['required_by'] = $requiredby;
if ($info->status == 1) {
$role = drush_role_get_class();
$data['permissions'] = $role->getModulePerms(drush_extension_get_name($info));
}
return $data;
}
/**
* Return an array with info of a theme.
*/
function _drush_pm_info_theme($info) {
$major_version = drush_drupal_major_version();
$data = _drush_pm_info_extension($info);
$data['core'] = $info->info['core'];
$data['php'] = $info->info['php'];
$data['engine'] = $info->info['engine'];
$data['base_theme'] = isset($info->base_themes) ? implode($info->base_themes, ', ') : '';
$regions = $info->info['regions'];
$data['regions'] = $regions;
$features = $info->info['features'];
$data['features'] = $features;
if (count($info->info['stylesheets']) > 0) {
$data['stylesheets'] = '';
foreach ($info->info['stylesheets'] as $media => $files) {
$files = array_keys($files);
$data['media '.$media] = $files;
}
}
if (count($info->info['scripts']) > 0) {
$scripts = array_keys($info->info['scripts']);
$data['scripts'] = $scripts;
}
return $data;
}

View file

@ -0,0 +1,275 @@
<?php
/**
* @file
* Drush PM drupal.org Git extension.
*/
use Drush\Log\LogLevel;
/**
* 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);
// We need to check for a git executable and then make sure version is >=1.7
// (avoid drush_shell_exec because we want to run this even in --simulated mode.)
$success = exec('git --version', $git);
$git_version_array = explode(" ", $git[0]);
$git_version = $git_version_array[2];
drush_set_context('DRUSH_DEBUG', $debug);
if (!$success) {
return drush_set_error('DRUSH_SHELL_COMMAND_NOT_FOUND', dt('git executable not found.'));
} elseif ($git_version < '1.7') {
return drush_set_error('GIT_VERSION_UNSUPPORTED', dt('Your git version !git_version is not supported; please upgrade to git 1.7 or later.', array('!git_version' => $git_version)));
}
// Check git_deploy is enabled. Only for bootstrapped sites.
if (drush_get_context('DRUSH_BOOTSTRAP_PHASE') >= DRUSH_BOOTSTRAP_DRUPAL_FULL) {
drush_include_engine('drupal', 'environment');
if (!drush_get_option('gitinfofile') && !drush_module_exists('git_deploy')) {
drush_log(dt('git package handler needs git_deploy module enabled to work properly.'), LogLevel::WARNING);
}
}
return TRUE;
}
/**
* Download a project.
*
* @param $request
* The project array with name, base and full (final) paths.
* @param $release
* The release details array from drupal.org.
*/
function package_handler_download_project(&$request, $release) {
if ($username = drush_get_option('gitusername')) {
// Uses SSH, which enables pushing changes back to git.drupal.org.
$repository = $username . '@git.drupal.org:project/' . $request['name'] . '.git';
}
else {
$repository = 'git://git.drupal.org/project/' . $request['name'] . '.git';
}
$request['repository'] = $repository;
$tag = $release['tag'];
// If the --cache option was given, create a new git reference cache of the
// remote repository, or update the existing cache to fetch recent changes.
if (drush_get_option('cache') && ($cachedir = drush_directory_cache())) {
$gitcache = $cachedir . '/git';
$projectcache = $gitcache . '/' . $request['name'] . '.git';
drush_mkdir($gitcache);
// Setup a new cache, if we don't have this project yet.
if (!file_exists($projectcache)) {
// --mirror works similar to --bare, but retrieves all tags, local
// branches, remote branches, and any other refs (notes, stashes, etc).
// @see http://stackoverflow.com/questions/3959924
$command = 'git clone --mirror';
if (drush_get_context('DRUSH_VERBOSE')) {
$command .= ' --verbose --progress';
}
$command .= ' %s %s';
drush_shell_cd_and_exec($gitcache, $command, $repository, $request['name'] . '.git');
}
// If we already have this project, update it to speed up subsequent clones.
else {
// A --mirror clone is fully synchronized with `git remote update` instead
// of `git fetch --all`.
// @see http://stackoverflow.com/questions/6150188
drush_shell_cd_and_exec($projectcache, 'git remote update');
}
$gitcache = $projectcache;
}
// Clone the repo into a temporary path.
$clone_path = drush_tempdir();
$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($clone_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($clone_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($clone_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($clone_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($clone_path, $command)) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to retrieve ' . $request['name'] . ' from git.drupal.org.');
}
}
}
return $clone_path;
}
/**
* 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 ((!empty($release['version_extra'])) && ($release['version_extra'] == 'dev')) {
// Update the branch of the development repository.
$commands[] = 'git pull';
$commands[] = drush_get_option('gitpullparams');
}
else {
// Use a stable repository.
$commands[] = 'git fetch';
$commands[] = drush_get_option('gitfetchparams');
$commands[] = ';';
$commands[] = 'git checkout';
$commands[] = drush_get_option('gitcheckoutparams');
$commands[] = $release['version'];
}
if (!drush_shell_cd_and_exec($request['full_project_path'], implode(' ', $commands))) {
return drush_set_error('DRUSH_PM_UNABLE_CHECKOUT', 'Unable to update ' . $request['name'] . ' from git.drupal.org.');
}
return TRUE;
}
/**
* Post download action.
*
* This action take place once the project is placed in its final location.
*
* Here we add the project as a git submodule.
*/
function package_handler_post_download($project, $release) {
if (drush_get_option('gitsubmodule', FALSE)) {
// Obtain the superproject path, then add as submodule.
if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git rev-parse --show-toplevel')) {
$output = drush_shell_exec_output();
$superproject = $output[0];
// Add the downloaded project as a submodule of its git superproject.
$command = array();
$command[] = 'git submodule add';
$command[] = drush_get_option('gitsubmoduleaddparams');
$command[] = $project['repository'];
// We need the submodule relative path.
$command[] = substr(realpath($project['full_project_path']), strlen(realpath($superproject)) + 1);
if (!drush_shell_cd_and_exec($superproject, implode(' ', $command))) {
return drush_set_error('DRUSH_PM_GIT_CHECKOUT_PROBLEMS', dt('Unable to add !name as a git submodule of !super.', array('!name' => $project['name'], '!super' => $superproject)));
}
}
else {
return drush_set_error('DRUSH_PM_GIT_SUBMODULE_PROBLEMS', dt('Unable to create !project as a git submodule: !dir is not in a Git repository.', array('!project' => $project['name'], '!dir' => dirname($project['full_project_path']))));
}
}
if (drush_get_option('gitinfofile', FALSE)) {
$matches = array();
if (preg_match('/^(.+).x-dev$/', $release['version'], $matches)) {
$full_version = drush_pm_git_drupalorg_compute_rebuild_version($project['full_project_path'], $matches[1]);
}
else {
$full_version = $release['version'];
}
if (drush_shell_cd_and_exec(dirname($project['full_project_path']), 'git log -1 --pretty=format:%ct')) {
$output = drush_shell_exec_output();
$datestamp = $output[0];
}
else {
$datestamp = time();
}
drush_pm_inject_info_file_metadata($project['full_project_path'], $project['name'], $full_version, $datestamp);
}
}
/**
* Helper function to compute the rebulid version string for a project.
*
* This does some magic in Git to find the latest release tag along
* the branch we're packaging from, count the number of commits since
* then, and use that to construct this fancy alternate version string
* which is useful for the version-specific dependency support in Drupal
* 7 and higher.
*
* NOTE: A similar function lives in git_deploy and in the drupal.org
* packaging script (see DrupalorgProjectPackageRelease.class.php inside
* drupalorg/drupalorg_project/plugins/release_packager). Any changes to the
* actual logic in here should probably be reflected in the other places.
*
* @param string $project_dir
* The full path to the root directory of the project to operate on.
* @param string $branch
* The branch that we're using for -dev. This should only include the
* core version, the dash, and the branch's major version (eg. '7.x-2').
*
* @return string
* The full 'rebuild version string' in the given Git checkout.
*/
function drush_pm_git_drupalorg_compute_rebuild_version($project_dir, $branch) {
$rebuild_version = '';
$branch_preg = preg_quote($branch);
if (drush_shell_cd_and_exec($project_dir, 'git describe --tags')) {
$shell_output = drush_shell_exec_output();
$last_tag = $shell_output[0];
// Make sure the tag starts as Drupal formatted (for eg.
// 7.x-1.0-alpha1) and if we are on a proper branch (ie. not master)
// then it's on that branch.
if (preg_match('/^(?<drupalversion>' . $branch_preg . '\.\d+(?:-[^-]+)?)(?<gitextra>-(?<numberofcommits>\d+-)g[0-9a-f]{7})?$/', $last_tag, $matches)) {
// If we found additional git metadata (in particular, number of commits)
// then use that info to build the version string.
if (isset($matches['gitextra'])) {
$rebuild_version = $matches['drupalversion'] . '+' . $matches['numberofcommits'] . 'dev';
}
// Otherwise, the branch tip is pointing to the same commit as the
// last tag on the branch, in which case we use the prior tag and
// add '+0-dev' to indicate we're still on a -dev branch.
else {
$rebuild_version = $last_tag . '+0-dev';
}
}
}
return $rebuild_version;
}

View file

@ -0,0 +1,116 @@
<?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.'));
}
return TRUE;
}
/**
* 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') {
$variant = drush_get_option('variant', 'full');
foreach ($release['files'] as $file) {
if ($file['variant'] == $variant && $file['archive_type'] == 'tar.gz') {
$release = array_merge($release, $file);
break;
}
}
}
// Add <date> to download link, so it is part of the cache key. Dev snapshots can then be cached forever.
$download_link = $release['download_link'];
if (strpos($release['download_link'], '-dev') !== FALSE) {
$download_link .= '?date=' . $release['date'];
}
// Cache for a year by default.
$cache_duration = (drush_get_option('cache', TRUE)) ? 86400*365 : 0;
// Prepare download path. On Windows file name cannot contain '?'.
// See http://drupal.org/node/1782444
$filename = str_replace('?', '_', basename($download_link));
$download_path = drush_tempdir() . '/' . $filename;
// Download the tarball.
$download_path = drush_download_file($download_link, $download_path, $cache_duration);
if ($download_path || drush_get_context('DRUSH_SIMULATE')) {
drush_log(dt('Downloading !filename was successful.', array('!filename' => $filename)));
}
else {
return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', dt('Unable to download !project to !path from !url.', array('!project' => $request['name'], '!path' => $download_path, '!url' => $download_link)));
}
// Check Md5 hash.
if (!drush_get_option('no-md5')) {
if (drush_op('md5_file', $download_path) !== $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) {
drush_delete_dir(drush_download_file_name($download_link, TRUE));
return drush_set_error('DRUSH_PM_FILE_CORRUPT', dt('File !filename is corrupt (wrong md5 checksum).', array('!filename' => $filename)));
}
else {
drush_log(dt('Md5 checksum of !filename verified.', array('!filename' => $filename)));
}
}
// Extract the tarball in place and return the full path to the untarred directory.
$download_base = dirname($download_path);
if (!$tar_file_list = drush_tarball_extract($download_path, $download_base, TRUE)) {
// An error has been logged.
return FALSE;
}
$tar_directory = drush_trim_path($tar_file_list[0]);
return $download_base . '/' . $tar_directory;
}
/**
* Update a project.
*
* @return bool
* Success or failure. An error message will be logged.
*/
function package_handler_update_project(&$request, $release) {
$download_path = package_handler_download_project($request, $release);
if ($download_path) {
return drush_move_dir($download_path, $request['full_project_path']);
}
else {
return FALSE;
}
}
/**
* 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,87 @@
<?php
use Drush\Log\LogLevel;
/**
* Implementation of drush_hook_COMMAND_validate().
*/
function drush_pm_projectinfo_validate() {
$status = drush_get_option('status');
if (!empty($status)) {
if (!in_array($status, array('enabled', 'disabled'), TRUE)) {
return drush_set_error('DRUSH_PM_INVALID_PROJECT_STATUS', dt('!status is not a valid project status.', array('!status' => $status)));
}
}
}
/**
* Implementation of drush_hook_COMMAND().
*/
function drush_pm_projectinfo() {
// Get specific requests.
$requests = pm_parse_arguments(func_get_args(), FALSE);
// Get installed extensions and projects.
$extensions = drush_get_extensions();
$projects = drush_get_projects($extensions);
// If user did not specify any projects, return them all
if (empty($requests)) {
$result = $projects;
}
else {
$result = array();
foreach ($requests as $name) {
if (array_key_exists($name, $projects)) {
$result[$name] = $projects[$name];
}
else {
drush_log(dt('!project was not found.', array('!project' => $name)), LogLevel::WARNING);
continue;
}
}
}
// Find the Drush commands that belong with each project.
foreach ($result as $name => $project) {
$drush_commands = pm_projectinfo_commands_in_project($project);
if (!empty($drush_commands)) {
$result[$name]['drush'] = $drush_commands;
}
}
// If user specified --drush, remove projects with no drush extensions
if (drush_get_option('drush')) {
foreach ($result as $name => $project) {
if (!array_key_exists('drush', $project)) {
unset($result[$name]);
}
}
}
// If user specified --status=1|0, remove projects with a distinct status.
if (($status = drush_get_option('status', FALSE)) !== FALSE) {
$status_code = ($status == 'enabled') ? 1 : 0;
foreach ($result as $name => $project) {
if ($project['status'] != $status_code) {
unset($result[$name]);
}
}
}
return $result;
}
function pm_projectinfo_commands_in_project($project) {
$drush_commands = array();
if (array_key_exists('path', $project)) {
$commands = drush_get_commands();
foreach ($commands as $commandname => $command) {
if (!array_key_exists("is_alias", $command) && ($command['path'] == $project['path'])) {
$drush_commands[] = $commandname;
}
}
}
return $drush_commands;
}

View file

@ -0,0 +1,409 @@
<?php
/**
* @file
* pm-updatecode command implementation.
*/
use Drush\Log\LogLevel;
/**
* 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() {
// In --pipe mode, just run pm-updatestatus and exit.
if (drush_get_context('DRUSH_PIPE')) {
drush_set_option('strict', 0);
return drush_invoke('pm-updatestatus');
}
$update_status = drush_get_engine('update_status');
// Get specific requests.
$requests = pm_parse_arguments(func_get_args(), FALSE);
// Print report of modules to update, and record
// result of that function in $update_info.
$updatestatus_options = array();
foreach (array('lock', 'unlock', 'lock-message', 'update-backend', 'check-disabled', 'security-only') as $option) {
$value = drush_get_option($option, FALSE);
if ($value) {
$updatestatus_options[$option] = $value;
}
}
$backend_options = array(
'integrate' => FALSE,
);
$values = drush_invoke_process("@self", 'pm-updatestatus', func_get_args(), $updatestatus_options, $backend_options);
if (!is_array($values) || $values['error_status']) {
return drush_set_error('pm-updatestatus failed.');
}
$last = $update_status->lastCheck();
drush_print(dt('Update information last refreshed: ') . ($last ? format_date($last) : dt('Never')));
drush_print($values['output']);
$update_info = $values['object'];
// Prevent update of core if --no-core was specified.
if (isset($update_info['drupal']) && drush_get_option('no-core', FALSE)) {
unset($update_info['drupal']);
drush_print(dt('Skipping core update (--no-core specified).'));
}
// Remove locked and non-updateable projects.
foreach ($update_info as $name => $project) {
if ((isset($project['locked']) && !isset($requests[$name])) || (!isset($project['updateable']) || !$project['updateable'])) {
unset($update_info[$name]);
}
}
// Do no updates in simulated mode.
if (drush_get_context('DRUSH_SIMULATE')) {
return drush_log(dt('No action taken in simulated mode.'), LogLevel::OK);
return TRUE;
}
$tmpfile = drush_tempnam('pm-updatecode.');
$core_update_available = FALSE;
if (isset($update_info['drupal'])) {
$drupal_project = $update_info['drupal'];
unset($update_info['drupal']);
// At present we need to update drupal core after non-core projects
// are updated.
if (empty($update_info)) {
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'] == DRUSH_UPDATESTATUS_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 projects are updated.\n"));
}
}
// If there are no releases to update, then print a final
// exit message.
if (empty($update_info)) {
if (drush_get_option('security-only')) {
return drush_log(dt('No security updates available.'), LogLevel::OK);
}
else {
return drush_log(dt('No code updates available.'), LogLevel::OK);
}
}
// Offer to update to the identified releases.
if (!pm_update_packages($update_info, $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.
*
* @return bool
* Success or failure. An error message will be logged.
*/
function _pm_update_core(&$project, $tmpfile) {
$release_info = drush_get_engine('release_info');
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...');
#TODO# Build the $request array from info in $project.
$request = pm_parse_request('drupal');
$release_info->get($request)->getReleaseNotes(NULL, 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();
}
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
// 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.
// On Drupal >=8 skip also directories in the document root.
if (drush_drupal_major_version() >= 8) {
$skip_list = array('sites', $project['path'], 'modules', 'profiles', 'themes');
}
else {
$skip_list = array('sites', $project['path']);
}
// Add non-writable directories: we can't move them around.
// We will also use $items_to_test later for $version_control check.
$items_to_test = drush_scan_directory($drupal_root, '/.*/', array_merge(array('.', '..'), $skip_list), 0, FALSE, 'basename', 0, TRUE);
foreach (array_keys($items_to_test) as $item) {
if (is_dir($item) && !is_writable($item)) {
$skip_list[] = $item;
unset($items_to_test[$item]);
}
elseif (is_link($item)) {
$skip_list[] = $item;
unset($items_to_test[$item]);
}
}
$project['skip_list'] = $skip_list;
// Move all files and folders in $drupal_root to the new 'core' directory
// except for the items in the skip list
_pm_update_move_files($drupal_root, $project['full_project_path'], $project['skip_list']);
// Set a context variable to indicate that rollback should reverse
// the _pm_update_move_files above.
drush_set_context('DRUSH_PM_DRUPAL_CORE', $project);
if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
return FALSE;
}
// Check we have a version control system, and it clears its pre-flight.
if (!$version_control->pre_update($project, $items_to_test)) {
return FALSE;
}
// Update core.
if (pm_update_project($project, $version_control) === FALSE) {
return FALSE;
}
// Take the updated files in the 'core' directory that have been updated,
// and move all except for the items in the skip list back to
// the drupal root.
_pm_update_move_files($project['full_project_path'], $drupal_root, $project['skip_list']);
drush_delete_dir($project['full_project_path']);
$project['full_project_path'] = $drupal_root;
// 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_finish($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) {
$release_info = drush_get_engine('release_info');
$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[DRUSH_UPDATESTATUS_NOT_SECURE])) {
if (isset($status[DRUSH_UPDATESTATUS_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...');
#TODO# Build the $request array from info in $project.
foreach (array_keys($update_info) as $project_name) {
$request = pm_parse_request($project_name);
$release_info->get($request)->getReleaseNotes(NULL, TRUE, $tmpfile);
}
}
// We print some warnings before the user confirms the update.
drush_print();
if (drush_get_option('no-backup', FALSE)) {
drush_print(dt("Note: You have selected to not store backups."));
}
else {
drush_print(dt("Note: A backup of your project will be stored to backups directory if it is not managed by a supported version control system."));
drush_print(dt('Note: If you have made any modifications to any file that belongs to one of these projects, you will have to migrate those modifications after updating.'));
}
if(!drush_confirm(dt('Do you really want to continue with the update process?'))) {
return drush_user_abort();
}
// Now we start the actual updating.
foreach($update_info as $project) {
if (empty($project['path'])) {
return drush_set_error('DRUSH_PM_UPDATING_NO_PROJECT_PATH', dt('The !project project path is not available, perhaps the !type is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'])));
}
drush_log(dt('Starting to update !project code at !dir...', array('!project' => $project['title'], '!dir' => $project['path'])));
// Define and check the full path to project directory and base (parent) directory.
$project['full_project_path'] = $drupal_root . '/' . $project['path'];
if (stripos($project['path'], $project['project_type']) === FALSE || !is_dir($project['full_project_path'])) {
return drush_set_error('DRUSH_PM_UPDATING_PATH_NOT_FOUND', dt('The !project directory could not be found within the !types directory at !full_project_path, perhaps the project is enabled but has been deleted from disk.', array('!project' => $project['name'], '!type' => $project['project_type'], '!full_project_path' => $project['full_project_path'])));
}
if (!$version_control = drush_pm_include_version_control($project['full_project_path'])) {
return FALSE;
}
// Check we have a version control system, and it clears its pre-flight.
if (!$version_control->pre_update($project)) {
return FALSE;
}
// Run update on one project.
if (pm_update_project($project, $version_control) === FALSE) {
return FALSE;
}
pm_update_finish($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.
* @return bool
* Success or failure. An error message will be logged.
*/
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 finished for one project.
*/
function pm_update_finish($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']], $project);
$version_control->post_update($project);
}
/**
* Rollback the update process.
*/
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']);
}
}

View file

@ -0,0 +1,246 @@
<?php
/**
* @file
* pm-updatestatus command implementation.
*/
/**
* Command callback. Displays update status info of installed projects.
*
* Pass specific projects as arguments, otherwise we show all that are
* updateable.
*/
function drush_pm_updatestatus() {
// Get specific requests.
$args = pm_parse_arguments(func_get_args(), FALSE);
// Get installed extensions and projects.
$extensions = drush_get_extensions();
$projects = drush_get_projects($extensions);
// Parse out project name and version.
$requests = array();
foreach ($args as $request) {
$request = pm_parse_request($request, NULL, $projects);
$requests[$request['name']] = $request;
}
// Get the engine instance.
$update_status = drush_get_engine('update_status');
// If the user doesn't provide a value for check-disabled option,
// and the update backend is 'drupal', use NULL, so the engine
// will respect update.module defaults.
$check_disabled_default = ($update_status->engine == 'drupal') ? NULL : FALSE;
$check_disabled = drush_get_option('check-disabled', $check_disabled_default);
$update_info = $update_status->getStatus($projects, $check_disabled);
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(
'name' => $name,
'label' => $extension->label,
'existing_version' => 'Unknown',
'status' => DRUSH_UPDATESTATUS_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]);
}
}
}
// Additionally 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])) && ($name != $extension->info['project'])) {
$requests[$extension->info['project']] = $requests[$name];
unset($requests[$name]);
}
}
// If specific project updates were requested then remove releases for all
// others.
$requested = func_get_args();
if (!empty($requested)) {
foreach ($update_info as $name => $project) {
if (!isset($requests[$name])) {
unset($update_info[$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(
'name' => $name,
'label' => $projects[$name]['label'],
'existing_version' => $projects[$name]['version'],
'status' => DRUSH_UPDATESTATUS_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 misspelled,...
else {
$update_info[$name] = array(
'name' => $name,
'label' => $name,
'existing_version' => 'Unknown',
'status'=> DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND,
);
}
}
}
// If specific versions were requested, match the requested release.
foreach ($requests as $name => $request) {
if (!empty($request['version'])) {
if (empty($update_info[$name]['releases'][$request['version']])) {
$update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND;
}
elseif ($request['version'] == $update_info[$name]['existing_version']) {
$update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT;
}
// TODO: should we warn/reject if this is a downgrade?
else {
$update_info[$name]['status'] = DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT;
$update_info[$name]['candidate_version'] = $request['version'];
}
}
}
// 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'));
// Build project updatable messages, set candidate version and mark
// 'updateable' in the project.
foreach ($update_info as $key => $project) {
switch($project['status']) {
case DRUSH_UPDATESTATUS_NOT_SECURE:
$status = dt('SECURITY UPDATE available');
pm_release_recommended($project);
break;
case DRUSH_UPDATESTATUS_REVOKED:
$status = dt('Installed version REVOKED');
pm_release_recommended($project);
break;
case DRUSH_UPDATESTATUS_NOT_SUPPORTED:
$status = dt('Installed version not supported');
pm_release_recommended($project);
break;
case DRUSH_UPDATESTATUS_NOT_CURRENT:
$status = dt('Update available');
pm_release_recommended($project);
break;
case DRUSH_UPDATESTATUS_CURRENT:
$status = dt('Up to date');
pm_release_recommended($project);
$project['updateable'] = FALSE;
break;
case DRUSH_UPDATESTATUS_NOT_CHECKED:
case DRUSH_UPDATESTATUS_NOT_FETCHED:
case DRUSH_UPDATESTATUS_FETCH_PENDING:
$status = dt('Unable to check status');
break;
case DRUSH_UPDATESTATUS_PROJECT_NOT_PACKAGED:
$status = $project['status_msg'];
break;
case DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_UPDATEABLE:
$status = dt('Project has no enabled extensions and can\'t be updated');
break;
case DRUSH_UPDATESTATUS_REQUESTED_PROJECT_NOT_FOUND:
$status = dt('Specified project not found');
break;
case DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_FOUND:
$status = dt('Specified version not found');
break;
case DRUSH_UPDATESTATUS_REQUESTED_VERSION_CURRENT:
$status = dt('Specified version already installed');
break;
case DRUSH_UPDATESTATUS_REQUESTED_VERSION_NOT_CURRENT:
$status = dt('Specified version available');
$project['updateable'] = TRUE;
break;
default:
$status = dt('Unknown');
break;
}
if (isset($project['locked'])) {
$status = $project['locked'] . " ($status)";
}
// Persist candidate_version in $update_info (plural).
if (empty($project['candidate_version'])) {
$update_info[$key]['candidate_version'] = $project['existing_version']; // Default to no change
}
else {
$update_info[$key]['candidate_version'] = $project['candidate_version'];
}
$update_info[$key]['status_msg'] = $status;
if (isset($project['updateable'])) {
$update_info[$key]['updateable'] = $project['updateable'];
}
}
// Filter projects to show.
return pm_project_filter($update_info, drush_get_option('security-only'));
}
/**
* Filter projects based on verbosity level and $security_only flag.
*
* @param array $update_info
* Update info for projects.
* @param bool $security_only
* Whether to select only projects with security updates.
*
* @return
* Array of projects matching filter criteria.
*/
function pm_project_filter($update_info, $security_only) {
$eligible = array();
foreach ($update_info as $key => $project) {
if ($security_only) {
if ($project['status'] == DRUSH_UPDATESTATUS_NOT_SECURE) {
$eligible[$key] = $project;
}
}
elseif (drush_get_context('DRUSH_VERBOSE')) {
$eligible[$key] = $project;
}
elseif ($project['status'] != DRUSH_UPDATESTATUS_CURRENT) {
$eligible[$key] = $project;
}
}
return $eligible;
}
/**
* Set a release to a recommended version (if available), and set as updateable.
*/
function pm_release_recommended(&$project) {
if (isset($project['recommended'])) {
$project['candidate_version'] = $project['recommended'];
$project['updateable'] = TRUE;
}
// If installed version is dev and the candidate version is older, choose
// latest dev as candidate.
if (($project['install_type'] == 'dev') && isset($project['candidate_version'])) {
if ($project['releases'][$project['candidate_version']]['date'] < $project['datestamp']) {
$project['candidate_version'] = $project['latest_dev'];
if ($project['releases'][$project['candidate_version']]['date'] <= $project['datestamp']) {
$project['candidate_version'] = $project['existing_version'];
$project['updateable'] = FALSE;
}
}
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Drush pm directory copy backup extension
*/
use Drush\Log\LogLevel;
class drush_version_control_backup implements drush_version_control {
/**
* Implementation of pre_update().
*/
public function pre_update(&$project, $items_to_test = array()) {
if (drush_get_option('no-backup', FALSE)) {
// Delete the project path to clean up files that should be removed
if (!drush_delete_dir($project['full_project_path'])) {
return 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."), LogLevel::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'])), LogLevel::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,137 @@
<?php
/**
* @file
* Drush pm Bazaar extension
*/
use Drush\Log\LogLevel;
class drush_version_control_bzr implements drush_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'), LogLevel::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,138 @@
<?php
/**
* @file
* Drush pm SVN extension
*/
use Drush\Log\LogLevel;
class drush_version_control_svn implements drush_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'), LogLevel::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,70 @@
<?php
// We hijack filter_init (which core filter module does not implement) as
// a convenient place to affect early changes.
if (!function_exists('filter_init')) {
// Check function_exists as a safety net in case it is added in future.
function filter_init() {
global $conf, $user;
// Inject values into the $conf array - will apply to all sites.
// This can be a useful place to apply generic development settings.
$conf_inject = unserialize(urldecode(runserver_env('RUNSERVER_CONF')));
// Merge in the injected conf, overriding existing items.
$conf = array_merge($conf, $conf_inject);
}
}
// We hijack system_watchdog (which core system module does not implement) as
// a convenient place to capture logs.
if (!function_exists('system_watchdog')) {
// Check function_exists as a safety net in case it is added in future.
function system_watchdog($log_entry = array()) {
$uid = $log_entry['user']->uid;
$message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array(
'!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
'!severity' => $log_entry['severity'],
'!type' => $log_entry['type'],
'!ip' => $log_entry['ip'],
'!request_uri' => $log_entry['request_uri'],
'!referer' => $log_entry['referer'],
'!uid' => $uid,
'!link' => strip_tags($log_entry['link']),
));
error_log($message);
}
}
// Get a $_SERVER key, or equivalent environment variable
// if it is not set in $_SERVER.
function runserver_env($key) {
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
else {
return getenv($key);
}
}
$url = parse_url($_SERVER["REQUEST_URI"]);
if (file_exists('.' . urldecode($url['path']))) {
// Serve the requested resource as-is.
return FALSE;
}
// Populate the "q" query key with the path, skip the leading slash.
$_GET['q'] = $_REQUEST['q'] = substr($url['path'], 1);
// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = runserver_env('RUNSERVER_BASE_URL');
// The built in webserver incorrectly sets $_SERVER['SCRIPT_NAME'] when URLs
// contain multiple dots (such as config entity IDs) in the path. Since this is
// a virtual resource, served by index.php set the script name explicitly.
// See https://github.com/drush-ops/drush/issues/2033 for more information.
$_SERVER['SCRIPT_NAME'] = '/index.php';
// Include the main index.php and let Drupal take over.
// n.b. Drush sets the cwd to the Drupal root during bootstrap.
include 'index.php';

View file

@ -0,0 +1,67 @@
<?php
// We hijack filter_init (which core filter module does not implement) as
// a convenient place to affect early changes.
if (!function_exists('filter_init')) {
// Check function_exists as a safety net in case it is added in future.
function filter_init() {
global $conf, $user;
// Inject values into the $conf array - will apply to all sites.
// This can be a useful place to apply generic development settings.
$conf_inject = unserialize(urldecode(runserver_env('RUNSERVER_CONF')));
// Merge in the injected conf, overriding existing items.
$conf = array_merge($conf, $conf_inject);
}
}
// We hijack system_watchdog (which core system module does not implement) as
// a convenient place to capture logs.
if (!function_exists('system_watchdog')) {
// Check function_exists as a safety net in case it is added in future.
function system_watchdog($log_entry = array()) {
$uid = $log_entry['user']->id();
$message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array(
'!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
'!severity' => $log_entry['severity'],
'!type' => $log_entry['type'],
'!ip' => $log_entry['ip'],
'!request_uri' => $log_entry['request_uri'],
'!referer' => $log_entry['referer'],
'!uid' => $uid,
'!link' => strip_tags($log_entry['link']),
));
error_log($message);
}
}
// Get a $_SERVER key, or equivalent environment variable
// if it is not set in $_SERVER.
function runserver_env($key) {
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
else {
return getenv($key);
}
}
$url = parse_url($_SERVER["REQUEST_URI"]);
if (file_exists('.' . urldecode($url['path']))) {
// Serve the requested resource as-is.
return FALSE;
}
// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = runserver_env('RUNSERVER_BASE_URL');
// The built in webserver incorrectly sets $_SERVER['SCRIPT_NAME'] when URLs
// contain multiple dots (such as config entity IDs) in the path. Since this is
// a virtual resource, served by index.php set the script name explicitly.
// See https://github.com/drush-ops/drush/issues/2033 for more information.
$_SERVER['SCRIPT_NAME'] = '/index.php';
// Include the main index.php and let Drupal take over.
// n.b. Drush sets the cwd to the Drupal root during bootstrap.
include 'index.php';

View file

@ -0,0 +1,68 @@
<?php
// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://127.0.0.1:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = runserver_env('RUNSERVER_BASE_URL');
// Complete $_GET['q'] for Drupal 6 with built in server
// - this uses the Drupal 7 method.
if (!isset($_GET['q']) && isset($_SERVER['REQUEST_URI'])) {
// This request is either a clean URL, or 'index.php', or nonsense.
// Extract the path from REQUEST_URI.
$request_path = strtok($_SERVER['REQUEST_URI'], '?');
$base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/'));
// Unescape and strip $base_path prefix, leaving q without a leading slash.
$_GET['q'] = substr(urldecode($request_path), $base_path_len + 1);
}
// We hijack filter_init (which core filter module does not implement) as
// a convenient place to affect early changes.
if (!function_exists('filter_init')) {
// Check function_exists as a safety net in case it is added in future.
function filter_init() {
global $conf, $user;
// Inject values into the $conf array - will apply to all sites.
// This can be a useful place to apply generic development settings.
$conf_inject = unserialize(urldecode(runserver_env('RUNSERVER_CONF')));
// Merge in the injected conf, overriding existing items.
$conf = array_merge($conf, $conf_inject);
}
}
// We hijack system_watchdog (which core system module does not implement) as
// a convenient place to capture logs.
if (!function_exists('system_watchdog')) {
// Check function_exists as a safety net in case it is added in future.
function system_watchdog($log_entry = array()) {
// Drupal <= 7.x defines VERSION. Drupal 8 defines \Drupal::VERSION instead.
if (defined('VERSION')) {
$uid = $log_entry['user']->uid;
}
else {
$uid = $log_entry['user']->id();
}
$message = strtr('Watchdog: !message | severity: !severity | type: !type | uid: !uid | !ip | !request_uri | !referer | !link', array(
'!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])),
'!severity' => $log_entry['severity'],
'!type' => $log_entry['type'],
'!ip' => $log_entry['ip'],
'!request_uri' => $log_entry['request_uri'],
'!referer' => $log_entry['referer'],
'!uid' => $uid,
'!link' => strip_tags($log_entry['link']),
));
error_log($message);
}
}
// Get a $_SERVER key, or equivalent environment variable
// if it is not set in $_SERVER.
function runserver_env($key) {
if (isset($_SERVER[$key])) {
return $_SERVER[$key];
}
else {
return getenv($key);
}
}

View file

@ -0,0 +1,220 @@
<?php
/**
* @file
* Built in http server commands.
*/
/**
* Implements hook_drush_help().
*/
function runserver_drush_help($section) {
switch ($section) {
case 'meta:runserver:title':
return dt("Runserver commands");
case 'meta:runserver:summary':
return dt('Launch the built-in PHP webserver.');
case 'drush:runserver':
return dt("Runs a lightweight built in http server for development.
- Don't use this for production, it is neither scalable nor secure for this use.
- If you run multiple servers simultaneously, you will need to assign each a unique port.
- Use Ctrl-C or equivalent to stop the server when complete.");
}
}
/**
* Implements hook_drush_command().
*/
function runserver_drush_command() {
$items = array();
$items['runserver'] = array(
'description' => 'Runs PHP\'s built-in http server for development.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
'arguments' => array(
'addr:port/path' => 'Host IP address and port number to bind to and path to open in web browser. Format is addr:port/path, default 127.0.0.1:8888, all elements optional. See examples for shorthand. Only opens a browser if a path is specified.',
),
'options' => array(
'variables' => 'Key-value array of variables to override in the $conf array for the running site. By default disables drupal_http_request_fails to avoid errors on Windows (which supports only one connection at a time). Comma delimited list of name=value pairs (or array in drushrc).',
'default-server' => 'A default addr:port/path to use for any values not specified as an argument.',
'user' => 'If opening a web browser, automatically log in as this user (user ID or username). Default is to log in as uid 1.',
'browser' => 'If opening a web browser, which browser to user (defaults to operating system default). Use --no-browser to avoid opening a browser.',
'dns' => 'Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.',
),
'aliases' => array('rs'),
'examples' => array(
'drush rs 8080' => 'Start runserver on 127.0.0.1, port 8080.',
'drush rs 10.0.0.28:80' => 'Start runserver on 10.0.0.28, port 80.',
'drush rs [::1]:80' => 'Start runserver on IPv6 localhost ::1, port 80.',
'drush rs --dns localhost:8888/user' => 'Start runserver on localhost (using rDNS to determine binding IP), port 8888, and open /user in browser.',
'drush rs /' => 'Start runserver on default IP/port (127.0.0.1, port 8888), and open / in browser.',
'drush rs --default-server=127.0.0.1:8080/ -' => 'Use a default (would be specified in your drushrc) that starts runserver on port 8080, and opens a browser to the front page. Set path to a single hyphen path in argument to prevent opening browser for this session.',
'drush rs :9000/admin' => 'Start runserver on 127.0.0.1, port 9000, and open /admin in browser. Note that you need a colon when you specify port and path, but no IP.',
),
);
return $items;
}
/**
* Callback for runserver command.
*/
function drush_core_runserver($uri = NULL) {
global $user, $base_url;
// Determine active configuration.
$uri = runserver_uri($uri);
if (!$uri) {
return FALSE;
}
// Remove any leading slashes from the path, since that is what url() expects.
$path = ltrim($uri['path'], '/');
// $uri['addr'] is a special field set by runserver_uri()
$hostname = $uri['host'];
$addr = $uri['addr'];
drush_set_context('DRUSH_URI', 'http://' . $hostname . ':' . $uri['port']);
// We pass in the currently logged in user (if set via the --user option),
// which will automatically log this user in the browser during the first
// request.
if (drush_get_option('user', FALSE) === FALSE) {
drush_set_option('user', 1);
}
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_LOGIN);
// We delete any registered files here, since they are not caught by Ctrl-C.
_drush_delete_registered_files();
// We set the effective base_url, since we have now detected the current site,
// and need to ensure generated URLs point to our runserver host.
// We also pass in the effective base_url to our auto_prepend_script via the
// CGI environment. This allows Drupal to generate working URLs to this http
// server, whilst finding the correct multisite from the HTTP_HOST header.
$base_url = 'http://' . $addr . ':' . $uri['port'];
$env['RUNSERVER_BASE_URL'] = $base_url;
// We pass in an array of $conf overrides using the same approach.
// This is available as an option for developers to pass in their own
// favorite $conf overrides (e.g. disabling css aggregation).
$current_override = drush_get_option_list('variables', array());
$override = array();
foreach ($current_override as $name => $value) {
if (is_numeric($name) && (strpos($value, '=') !== FALSE)) {
list($name, $value) = explode('=', $value, 2);
}
$override[$name] = $value;
}
$env['RUNSERVER_CONF'] = urlencode(serialize($override));
// We log in with the specified user ID (if set) via the password reset URL.
$user_message = '';
$usersingle = drush_user_get_class()->getCurrentUserAsSingle();
if ($usersingle->id()) {
$browse = $usersingle->passResetUrl($path);
$user_message = ', logged in as ' . $usersingle->getUsername();
}
else {
$browse = drush_url($path);
}
drush_print(dt('HTTP server listening on !addr, port !port (see http://!hostname:!port/!path), serving site !site!user...', array('!addr' => $addr, '!hostname' => $hostname, '!port' => $uri['port'], '!path' => $path, '!site' => drush_get_context('DRUSH_DRUPAL_SITE', 'default'), '!user' => $user_message)));
// Start php 5.4 builtin server.
// Store data used by runserver-prepend.php in the shell environment.
foreach ($env as $key => $value) {
putenv($key . '=' . $value);
}
if (!empty($uri['path'])) {
// Start a browser if desired. Include a 2 second delay to allow the
// server to come up.
drush_start_browser($browse, 2);
}
// Start the server using 'php -S'.
if (drush_drupal_major_version() >= 8) {
$extra = ' "' . __DIR__ . '/d8-rs-router.php"';
}
elseif (drush_drupal_major_version() == 7) {
$extra = ' "' . __DIR__ . '/d7-rs-router.php"';
}
else {
$extra = ' --define auto_prepend_file="' . __DIR__ . '/runserver-prepend.php"';
}
$root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
drush_shell_exec_interactive('cd %s && %s -S ' . $addr . ':' . $uri['port']. $extra, $root, drush_get_option('php', 'php'));
}
/**
* Determine the URI to use for this server.
*/
function runserver_uri($uri) {
$drush_default = array(
'host' => '127.0.0.1',
'port' => '8888',
'path' => '',
);
$user_default = runserver_parse_uri(drush_get_option('default-server', ''));
$site_default = runserver_parse_uri(drush_get_option('uri', ''));
$uri = runserver_parse_uri($uri);
if (is_array($uri)) {
// Populate defaults.
$uri = $uri + $user_default + $site_default + $drush_default;
if (ltrim($uri['path'], '/') == '-') {
// Allow a path of a single hyphen to clear a default path.
$uri['path'] = '';
}
// Determine and set the new URI.
$uri['addr'] = $uri['host'];
if (drush_get_option('dns', FALSE)) {
if (ip2long($uri['host'])) {
$uri['host'] = gethostbyaddr($uri['host']);
}
else {
$uri['addr'] = gethostbyname($uri['host']);
}
}
}
return $uri;
}
/**
* Parse a URI or partial URI (including just a port, host IP or path).
*
* @param string $uri
* String that can contain partial URI.
*
* @return array
* URI array as returned by parse_url.
*/
function runserver_parse_uri($uri) {
if (empty($uri)) {
return array();
}
if ($uri[0] == ':') {
// ':port/path' shorthand, insert a placeholder hostname to allow parsing.
$uri = 'placeholder-hostname' . $uri;
}
// FILTER_VALIDATE_IP expects '[' and ']' to be removed from IPv6 addresses.
// We check for colon from the right, since IPv6 addresses contain colons.
$to_path = trim(substr($uri, 0, strpos($uri, '/')), '[]');
$to_port = trim(substr($uri, 0, strrpos($uri, ':')), '[]');
if (filter_var(trim($uri, '[]'), FILTER_VALIDATE_IP) || filter_var($to_path, FILTER_VALIDATE_IP) || filter_var($to_port, FILTER_VALIDATE_IP)) {
// 'IP', 'IP/path' or 'IP:port' shorthand, insert a schema to allow parsing.
$uri = 'http://' . $uri;
}
$uri = parse_url($uri);
if (empty($uri)) {
return drush_set_error('RUNSERVER_INVALID_ADDRPORT', dt('Invalid argument - should be in the "host:port/path" format, numeric (port only) or non-numeric (path only).'));
}
if (count($uri) == 1 && isset($uri['path'])) {
if (is_numeric($uri['path'])) {
// Port only shorthand.
$uri['port'] = $uri['path'];
unset($uri['path']);
}
}
if (isset($uri['host']) && $uri['host'] == 'placeholder-hostname') {
unset($uri['host']);
}
return $uri;
}

View file

@ -0,0 +1,653 @@
<?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.');
case 'drush:sql-sanitize':
return dt('Run sanitization operations on the current database. You can add more sanitization to this command by implementing hook_drush_sql_sync_sanitize().');
}
}
/**
* Implementation of hook_drush_command().
*/
function sql_drush_command() {
$options['database'] = array(
'description' => 'The DB connection key if using multiple connections in settings.php.',
'example-value' => 'key',
);
$db_url['db-url'] = array(
'description' => 'A Drupal 6 style database URL.',
'example-value' => 'mysql://root:pass@127.0.0.1/db',
);
$options['target'] = array(
'description' => 'The name of a target within the specified database connection. Defaults to \'default\'.',
'example-value' => 'key',
// Gets unhidden in help_alter(). We only want to show this to D7 users but have to
// declare it here since some commands do not bootstrap fully.
'hidden' => TRUE,
);
$items['sql-drop'] = array(
'description' => 'Drop all tables in a given database.',
'arguments' => array(
),
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'options' => array(
'yes' => 'Skip confirmation and proceed.',
'result-file' => array(
'description' => 'Save to a file. The file should be relative to Drupal root. Recommended.',
'example-value' => '/path/to/file',
),
) + $options + $db_url,
'topics' => array('docs-policy'),
);
$items['sql-conf'] = array(
'description' => 'Print database connection details using print_r().',
'hidden' => TRUE,
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'options' => array(
'all' => 'Show all database connections, instead of just one.',
'show-passwords' => 'Show database password.',
) + $options,
'outputformat' => array(
'default' => 'print-r',
'pipe-format' => 'var_export',
'private-fields' => 'password',
),
);
$items['sql-connect'] = array(
'description' => 'A string for connecting to the DB.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'options' => $options + $db_url + array(
'extra' => array(
'description' => 'Add custom options to the connect string.',
'example-value' => '--skip-column-names',
),
),
'examples' => array(
'`drush sql-connect` < example.sql' => 'Bash: Import SQL statements from a file into the current database.',
'eval (drush sql-connect) < example.sql' => 'Fish: Import SQL statements from a file into the current database.',
),
);
$items['sql-create'] = array(
'description' => 'Create a database.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'examples' => array(
'drush sql-create' => 'Create the database for the current site.',
'drush @site.test sql-create' => 'Create the database as specified for @site.test.',
'drush sql-create --db-su=root --db-su-pw=rootpassword --db-url="mysql://drupal_db_user:drupal_db_password@127.0.0.1/drupal_db"' =>
'Create the database as specified in the db-url option.'
),
'options' => array(
'db-su' => 'Account to use when creating a new database. Optional.',
'db-su-pw' => 'Password for the "db-su" account. Optional.',
) + $options + $db_url,
);
$items['sql-dump'] = array(
'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'examples' => array(
'drush sql-dump --result-file=../18.sql' => 'Save SQL dump to the directory above Drupal root.',
'drush sql-dump --skip-tables-key=common' => 'Skip standard tables. @see example.drushrc.php',
'drush sql-dump --extra=--no-data' => 'Pass extra option to dump command.',
),
'options' => drush_sql_get_table_selection_options() + array(
'result-file' => array(
'description' => 'Save to a file. The file should be relative to Drupal root. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.',
'example-value' => '/path/to/file',
'value' => 'optional',
),
'create-db' => array('hidden' => TRUE, 'description' => 'Omit DROP TABLE statements. Postgres and Oracle only. Used by sql-sync, since including the DROP TABLE statements interfere with the import when the database is created.'),
'data-only' => 'Dump data without statements to create any of the schema.',
'ordered-dump' => 'Order by primary key and add line breaks for efficient diff in revision control. Slows down the dump. Mysql only.',
'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.',
'extra' => 'Add custom options to the dump command.',
) + $options + $db_url,
);
$items['sql-query'] = array(
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'description' => 'Execute a query against a database.',
'examples' => array(
'drush sql-query "SELECT * FROM users WHERE uid=1"' => 'Browse user record. Table prefixes, if used, must be added to table names by hand.',
'drush sql-query --db-prefix "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored. Caution: curly-braces will be stripped from all portions of the query.',
'`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
'drush sql-query --file=example.sql' => 'Alternate way to import sql statements from a file.',
),
'arguments' => array(
'query' => 'An SQL query. Ignored if \'file\' is provided.',
),
'options' => array(
'result-file' => array(
'description' => 'Save to a file. The file should be relative to Drupal root. Optional.',
'example-value' => '/path/to/file',
),
'file' => 'Path to a file containing the SQL to be run. Gzip files are accepted.',
'extra' => array(
'description' => 'Add custom options to the database connection command.',
'example-value' => '--skip-column-names',
),
'db-prefix' => 'Enable replacement of braces in your query.',
'db-spec' => array(
'description' => 'A database specification',
'hidden' => TRUE, // Hide since this is only used with --backend calls.
)
) + $options + $db_url,
'aliases' => array('sqlq'),
);
$items['sql-cli'] = array(
'description' => "Open a SQL command-line interface using Drupal's credentials.",
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
// 'options' => $options + $db_url,
'allow-additional-options' => array('sql-connect'),
'aliases' => array('sqlc'),
'examples' => array(
'drush sql-cli' => "Open a SQL command-line interface using Drupal's credentials.",
'drush sql-cli --extra=-A' => "Open a SQL CLI and skip reading table information.",
),
'remote-tty' => TRUE,
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function sql_drush_help_alter(&$command) {
// Drupal 7+ only options.
if (drush_drupal_major_version() >= 7) {
if ($command['commandfile'] == 'sql') {
unset($command['options']['target']['hidden']);
}
}
}
/**
* Safely bootstrap Drupal to the point where we can
* access the database configuration.
*/
function drush_sql_bootstrap_database_configuration() {
// Under Drupal 7, if the database is configured but empty, then
// DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION will throw an exception.
// If this happens, we'll just catch it and continue.
// TODO: Fix this in the bootstrap, per http://drupal.org/node/1996004
try {
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
}
catch (Exception $e) {
}
}
/**
* Check whether further bootstrap is needed. If so, do it.
*/
function drush_sql_bootstrap_further() {
if (!drush_get_option(array('db-url', 'db-spec'))) {
drush_sql_bootstrap_database_configuration();
}
}
/**
* Command callback. Displays the Drupal site's database connection string.
*/
function drush_sql_conf() {
drush_sql_bootstrap_database_configuration();
if (drush_get_option('all')) {
$sqlVersion = drush_sql_get_version();
return $sqlVersion->getAll();
}
else {
$sql = drush_sql_get_class();
return $sql->db_spec();
}
}
/**
* Command callback. Emits a connect string.
*/
function drush_sql_connect() {
drush_sql_bootstrap_further();
$sql = drush_sql_get_class();
return $sql->connect(FALSE);
}
/**
* Command callback. Create a database.
*/
function drush_sql_create() {
drush_sql_bootstrap_further();
$sql = drush_sql_get_class();
$db_spec = $sql->db_spec();
// Prompt for confirmation.
if (!drush_get_context('DRUSH_SIMULATE')) {
// @todo odd - maybe for sql-sync.
$txt_destination = (isset($db_spec['remote-host']) ? $db_spec['remote-host'] . '/' : '') . $db_spec['database'];
drush_print(dt("Creating database !target. Any possible existing database will be dropped!", array('!target' => $txt_destination)));
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
return $sql->createdb(TRUE);
}
/**
* Command callback. Outputs the entire Drupal database in SQL format using mysqldump or equivalent.
*/
function drush_sql_dump() {
drush_sql_bootstrap_further();
$sql = drush_sql_get_class();
return $sql->dump(drush_get_option('result-file', FALSE));
}
/**
* Construct an array that places table names in appropriate
* buckets based on whether the table is to be skipped, included
* for structure only, or have structure and data dumped.
* The keys of the array are:
* - skip: tables to be skipped completed in the dump
* - structure: tables to only have their structure i.e. DDL dumped
* - tables: tables to have structure and data dumped
*
* @return array
* An array of table names with each table name in the appropriate
* element of the array.
*/
function drush_sql_get_table_selection() {
// Skip large core tables if instructed. Used by 'sql-drop/sql-dump/sql-sync' commands.
$skip_tables = _drush_sql_get_raw_table_list('skip-tables');
// Skip any structure-tables as well.
$structure_tables = _drush_sql_get_raw_table_list('structure-tables');
// Dump only the specified tables. Takes precedence over skip-tables and structure-tables.
$tables = _drush_sql_get_raw_table_list('tables');
return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables);
}
function drush_sql_get_table_selection_options() {
return 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.',
'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.',
'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
);
}
/**
* Expand wildcard tables.
*
* @param array $tables
* An array of table names, some of which may contain wildcards (`*`).
* @param array $db_tables
* An array with all the existing table names in the current database.
* @return
* $tables array with wildcards resolved to real table names.
*/
function drush_sql_expand_wildcard_tables($tables, $db_tables) {
// Table name expansion based on `*` wildcard.
$expanded_db_tables = array();
foreach ($tables as $k => $table) {
// Only deal with table names containing a wildcard.
if (strpos($table, '*') !== FALSE) {
$pattern = '/^' . str_replace('*', '.*', $table) . '$/i';
// Merge those existing tables which match the pattern with the rest of
// the expanded table names.
$expanded_db_tables += preg_grep($pattern, $db_tables);
}
}
return $expanded_db_tables;
}
/**
* Filters tables.
*
* @param array $tables
* An array of table names to filter.
* @param array $db_tables
* An array with all the existing table names in the current database.
* @return
* An array with only valid table names (i.e. all of which actually exist in
* the database).
*/
function drush_sql_filter_tables($tables, $db_tables) {
// Ensure all the tables actually exist in the database.
foreach ($tables as $k => $table) {
if (!in_array($table, $db_tables)) {
unset($tables[$k]);
}
}
return $tables;
}
/**
* Given the table names in the input array that may contain wildcards (`*`),
* expand the table names so that the array returned only contains table names
* that exist in the database.
*
* @param array $tables
* An array of table names where the table names may contain the
* `*` wildcard character.
* @param array $db_tables
* The list of tables present in a database.
* @return array
* An array of tables with non-existant tables removed.
*/
function _drush_sql_expand_and_filter_tables($tables, $db_tables) {
$expanded_tables = drush_sql_expand_wildcard_tables($tables, $db_tables);
$tables = drush_sql_filter_tables(array_merge($tables, $expanded_tables), $db_tables);
$tables = array_unique($tables);
sort($tables);
return $tables;
}
/**
* Consult the specified options and return the list of tables
* specified.
*
* @param option_name
* The option name to check: skip-tables, structure-tables
* or tables. This function will check both *-key and *-list,
* and, in the case of sql-sync, will also check target-*
* and source-*, to see if an alias set one of these options.
* @returns array
* Returns an array of tables based on the first option
* found, or an empty array if there were no matches.
*/
function _drush_sql_get_raw_table_list($option_name) {
foreach(array('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) {
foreach(explode(',',$prefix_list) as $prefix) {
$key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context);
foreach(explode(',', $key_list) as $key) {
$all_tables = drush_get_option($option_name, array());
if (array_key_exists($key, $all_tables)) {
return $all_tables[$key];
}
if ($option_name != 'tables') {
$all_tables = drush_get_option('tables', array());
if (array_key_exists($key, $all_tables)) {
return $all_tables[$key];
}
}
}
$table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context);
if (isset($table_list)) {
return empty($table_list) ? array() : explode(',', $table_list);
}
}
}
return array();
}
/**
* Command callback. Executes the given SQL query on the Drupal database.
*/
function drush_sql_query($query = NULL) {
drush_sql_bootstrap_further();
$filename = drush_get_option('file', NULL);
// Enable prefix processing when db-prefix option is used.
if (drush_get_option('db-prefix')) {
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
}
if (drush_get_context('DRUSH_SIMULATE')) {
if ($query) {
drush_print(dt('Simulating sql-query: !q', array('!q' => $query)));
}
else {
drush_print(dt('Simulating sql-import from !f', array('!f' => drush_get_option('file'))));
}
}
else {
$sql = drush_sql_get_class(drush_get_option('db-spec'));
$result = $sql->query($query, $filename, drush_get_option('result-file'));
if (!$result) {
return drush_set_error('DRUSH_SQL_NO_QUERY', dt('Query failed.'));
}
drush_print(implode("\n", drush_shell_exec_output()));
}
return TRUE;
}
/**
* Command callback. Drops all tables in the database.
*/
function drush_sql_drop() {
drush_sql_bootstrap_further();
$sql = drush_sql_get_class();
$db_spec = $sql->db_spec();
if (!drush_confirm(dt('Do you really want to drop all tables in the database !db?', array('!db' => $db_spec['database'])))) {
return drush_user_abort();
}
$tables = $sql->listTables();
return $sql->drop($tables);
}
/**
* Command callback. Launches console a DB backend.
*/
function drush_sql_cli() {
drush_sql_bootstrap_further();
$sql = drush_sql_get_class();
$result = !drush_shell_proc_open($sql->connect());
if (!$result) {
drush_set_error('DRUSH_SQL_CLI_ERROR', dt('SQL client error occurred.'));
}
return $result;
}
/**
* 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 = '';
$operations = drush_get_context('post-sync-ops');
if (!empty($operations)) {
$messages = dt('The following operations will be done on the target database:') . "\n";
$bullets = array_column($operations, 'message');
$messages .= " * " . implode("\n * ", $bullets) . "\n";
}
return $messages;
}
/**
* Wrapper for drush_get_class; instantiates an driver-specific instance
* of SqlBase class.
*
* @param array $db_spec
* If known, specify a $db_spec that the class can operate with.
*
* @throws \Drush\Sql\SqlException
*
* @return Drush\Sql\SqlBase
*/
function drush_sql_get_class($db_spec = NULL) {
$database = drush_get_option('database', 'default');
$target = drush_get_option('target', 'default');
// Try a few times to quickly get $db_spec.
if (!empty($db_spec)) {
if (!empty($db_spec['driver'])) {
return drush_get_class(array('Drush\Sql\Sql', 'Drupal\Driver\Database\\' . $db_spec['driver'] . '\Drush'), array($db_spec), array($db_spec['driver']));
}
}
elseif ($url = drush_get_option('db-url')) {
$url = is_array($url) ? $url[$database] : $url;
$db_spec = drush_convert_db_from_db_url($url);
$db_spec['db_prefix'] = drush_get_option('db-prefix');
return drush_sql_get_class($db_spec);
}
elseif (($databases = drush_get_option('databases')) && (array_key_exists($database, $databases)) && (array_key_exists($target, $databases[$database]))) {
$db_spec = $databases[$database][$target];
return drush_sql_get_class($db_spec);
}
else {
// No parameter or options provided. Determine $db_spec ourselves.
if ($sqlVersion = drush_sql_get_version()) {
if ($db_spec = $sqlVersion->get_db_spec()) {
return drush_sql_get_class($db_spec);
}
}
}
throw new \Drush\Sql\SqlException('Unable to find a matching SQL Class. Drush cannot find your database connection details.');
}
/**
* Wrapper for drush_get_class; instantiates a Drupal version-specific instance
* of SqlVersion class.
*
* @return Drush\Sql\SqlVersion
*/
function drush_sql_get_version() {
return drush_get_class('Drush\Sql\Sql', array(), array(drush_drupal_major_version())) ?: NULL;
}
/**
* Implements hook_drush_sql_sync_sanitize().
*
* Sanitize usernames, passwords, and sessions 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()
* in mymodule.drush.inc 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/7/8 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);
$databases = sitealias_get_databases_from_record($site_settings);
$major_version = drush_drupal_major_version();
$wrap_table_name = (bool) drush_get_option('db-prefix');
$user_table_updates = array();
$message_list = array();
// Sanitize passwords.
$newpassword = drush_get_option(array('sanitize-password', 'destination-sanitize-password'), 'password');
if ($newpassword != 'no' && $newpassword !== 0) {
$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) {
$core = DRUSH_DRUPAL_CORE;
include_once $core . '/includes/password.inc';
include_once $core . '/includes/bootstrap.inc';
$hash = user_hash_password($newpassword);
$pw_op = "'$hash'";
}
else {
// D8+. Mimic Drupal's /scripts/password-hash.sh
drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
$password_hasher = \Drupal::service('password');
$hash = $password_hasher->hash($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.localdomain');
if ($newemail != 'no' && $newemail !== 0) {
if (strpos($newemail, '%') !== FALSE) {
// We need a different sanitization query for Postgres and Mysql.
$db_driver = $databases['default']['default']['driver'];
if ($db_driver == 'pgsql') {
$email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '");
$newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
}
elseif ($db_driver == 'mssql') {
$email_map = array('%uid' => "' + uid + '", '%mail' => "' + replace(mail, '@', '_') + '", '%name' => "' + replace(name, ' ', '_') + '");
$newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
}
else {
$email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '");
$newmail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')";
}
$user_table_updates[] = "mail = $newmail, init = $newmail";
}
else {
$user_table_updates[] = "mail = '$newemail', init = '$newemail'";
}
$message_list[] = 'email addresses';
}
if (!empty($user_table_updates)) {
$table = $major_version >= 8 ? 'users_field_data' : 'users';
if ($wrap_table_name) {
$table = "{{$table}}";
}
$sanitize_query = "UPDATE {$table} SET " . implode(', ', $user_table_updates) . " WHERE uid > 0;";
drush_sql_register_post_sync_op('user-email', dt('Reset !message in !table table', array('!message' => implode(' and ', $message_list), '!table' => $table)), $sanitize_query);
}
$sanitizer = new \Drush\Commands\core\SanitizeCommands();
$sanitizer->doSanitize($major_version);
}

View file

@ -0,0 +1,283 @@
<?php
use Drush\Log\LogLevel;
use Webmozart\PathUtil\Path;
/**
* Implementation of hook_drush_command().
*/
function sqlsync_drush_command() {
$items['sql-sync'] = array(
'description' => 'Copies the database contents from a source site to a target site. Transfers the database dump via rsync.',
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'drush dependencies' => array('sql', 'core'), // core-rsync.
'package' => 'sql',
'examples' => array(
'drush sql-sync @source @target' => 'Copy the database from the site with the alias "source" to the site with the alias "target".',
'drush sql-sync prod dev' => 'Copy the database from the site in /sites/prod to the site in /sites/dev (multisite installation).',
),
'arguments' => array(
'source' => 'A site-alias or the name of a subdirectory within /sites whose database you want to copy from.',
'target' => 'A site-alias or the name of a subdirectory within /sites whose database you want to replace.',
),
'required-arguments' => TRUE,
'options' => drush_sql_get_table_selection_options() + array(
// '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.',
'no-sync' => 'Do not rsync the database dump file from source to target.',
'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".',
'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' => array(
'description' => 'The destination for the dump file, or the path to the dump file when --no-dump is specified.',
'example-value' => '/dumpdir/db.sql',
),
'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.',
'source-target' => array(
'description' => 'A key within the SOURCE database identifying a particular server in the database group.',
'example-value' => 'key',
// Gets unhidden in help_alter(). We only want to show to D7+ users but have to
// declare it here since this command does not bootstrap fully.
'hidden' => TRUE,
),
'target-db-url' => '',
'target-remote-port' => '',
'target-remote-host' => '',
'target-dump' => array(
'description' => 'A path for saving the dump file on target. Mandatory when using --no-sync.',
'example-value' => '/dumpdir/db.sql.gz',
),
'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.',
'target-target' => array(
'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.',
'example-value' => 'key',
// Gets unhidden in help_alter(). We only want to show to D7+ users but have to
// declare it here since this command does not bootstrap fully.
'hidden' => TRUE,
),
'create-db' => 'Create a new database before importing the database dump on the target machine.',
'db-su' => array(
'description' => 'Account to use when creating a new database. Optional.',
'example-value' => 'root',
),
'db-su-pw' => array(
'description' => 'Password for the "db-su" account. Optional.',
'example-value' => 'pass',
),
// 'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.',
'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync.',
),
'sub-options' => array(
'sanitize' => drupal_sanitize_options() + array(
'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations',
),
),
'topics' => array('docs-aliases', 'docs-policy', 'docs-example-sync-via-http', 'docs-example-sync-extension'),
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function sqlsync_drush_help_alter(&$command) {
// Drupal 7+ only options.
if (drush_drupal_major_version() >= 7) {
if ($command['command'] == 'sql-sync') {
unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']);
}
}
}
/**
* Command argument complete callback.
*
* @return
* Array of available site aliases.
*/
function sql_sql_sync_complete() {
return array('values' => array_keys(_drush_sitealias_all_list()));
}
/*
* Implements COMMAND hook init.
*/
function drush_sql_sync_init($source, $destination) {
// Try to get @self defined when --uri was not provided.
drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
// 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);
// Apply command-specific options.
drush_sitealias_command_default_options($source_settings, 'source-');
drush_sitealias_command_default_options($destination_settings, 'target-');
}
/*
* A command validate callback.
*/
function drush_sqlsync_sql_sync_validate($source, $destination) {
// Get destination info for confirmation prompt.
$source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
$destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
$source_db_spec = drush_sitealias_get_db_spec($source_settings, FALSE, 'source-');
$target_db_spec = drush_sitealias_get_db_spec($destination_settings, FALSE, 'target-');
$txt_source = (isset($source_db_spec['remote-host']) ? $source_db_spec['remote-host'] . '/' : '') . $source_db_spec['database'];
$txt_destination = (isset($target_db_spec['remote-host']) ? $target_db_spec['remote-host'] . '/' : '') . $target_db_spec['database'];
// Validate.
if (empty($source_db_spec)) {
if (empty($source_settings)) {
return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for source !source', array('!source' => $source)));
}
return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for source !source', array('!source' => $source)));
}
if (empty($target_db_spec)) {
if (empty($destination_settings)) {
return drush_set_error('DRUSH_ALIAS_NOT_FOUND', dt('Error: no alias record could be found for target !destination', array('!destination' => $destination)));
}
return drush_set_error('DRUSH_DATABASE_NOT_FOUND', dt('Error: no database record could be found for target !destination', array('!destination' => $destination)));
}
if (drush_get_option('no-dump') && !drush_get_option('source-dump')) {
return drush_set_error('DRUSH_SOURCE_DUMP_MISSING', dt('The --source-dump option must be supplied when --no-dump is specified.'));
}
if (drush_get_option('no-sync') && !drush_get_option('target-dump')) {
return drush_set_error('DRUSH_TARGET_DUMP_MISSING', dt('The --target-dump option must be supplied when --no-sync is specified.'));
}
if (!drush_get_context('DRUSH_SIMULATE')) {
drush_print(dt("You will destroy data in !target and replace with data from !source.", array('!source' => $txt_source, '!target' => $txt_destination)));
// @todo Move sanitization prompts to here. They currently show much later.
if (!drush_confirm(dt('Do you really want to continue?'))) {
return drush_user_abort();
}
}
}
/*
* A command callback.
*/
function drush_sqlsync_sql_sync($source, $destination) {
$source_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($source), 'source-');
$destination_settings = drush_sitealias_overlay_options(drush_sitealias_get_record($destination), 'target-');
$source_is_local = !array_key_exists('remote-host', $source_settings) || drush_is_local_host($source_settings);
$destination_is_local = !array_key_exists('remote-host', $destination_settings) || drush_is_local_host($destination_settings);
// These options are passed along to subcommands like sql-create, sql-dump, sql-query, sql-sanitize, ...
$source_options = drush_get_merged_prefixed_options('source-');
$target_options = drush_get_merged_prefixed_options('target-');
$backend_options = array();
// @todo drush_redispatch_get_options() assumes you will execute same command. Not good.
$global_options = drush_redispatch_get_options() + array(
'strict' => 0,
);
// We do not want to include root or uri here. If the user
// provided -r or -l, their key has already been remapped to
// 'root' or 'uri' by the time we get here.
unset($global_options['root']);
unset($global_options['uri']);
if (drush_get_context('DRUSH_SIMULATE')) {
$backend_options['backend-simulate'] = TRUE;
}
// Create destination DB if needed.
if (drush_get_option('create-db')) {
drush_log(dt('Starting to create database on Destination.'), LogLevel::OK);
$return = drush_invoke_process($destination, 'sql-create', array(), $global_options + $target_options, $backend_options);
if ($return['error_status']) {
return drush_set_error('DRUSH_SQL_CREATE_FAILED', dt('sql-create failed.'));
}
}
// Perform sql-dump on source unless told otherwise.
$options = $global_options + $source_options + array(
'gzip' => TRUE,
'result-file' => drush_get_option('source-dump', TRUE),
// 'structure-tables-list' => 'cache*', // Do we want to default to this?
);
if (!drush_get_option('no-dump')) {
drush_log(dt('Starting to dump database on Source.'), LogLevel::OK);
$return = drush_invoke_process($source, 'sql-dump', array(), $options, $backend_options);
if ($return['error_status']) {
return drush_set_error('DRUSH_SQL_DUMP_FAILED', dt('sql-dump failed.'));
}
else {
$source_dump_path = $return['object'];
if (!is_string($source_dump_path)) {
return drush_set_error('DRUSH_SQL_DUMP_FILE_NOT_REPORTED', dt('The Drush sql-dump command did not report the path to the dump file produced. Try upgrading the version of Drush you are using on the source machine.'));
}
}
}
else {
$source_dump_path = drush_get_option('source-dump');
}
$do_rsync = !drush_get_option('no-sync');
// Determine path/to/dump on destination.
if (drush_get_option('target-dump')) {
$destination_dump_path = drush_get_option('target-dump');
$rsync_options['yes'] = TRUE; // @temporary: See https://github.com/drush-ops/drush/pull/555
}
elseif ($source_is_local && $destination_is_local) {
$destination_dump_path = $source_dump_path;
$do_rsync = FALSE;
}
else {
$tmp = '/tmp'; // Our fallback plan.
drush_log(dt('Starting to discover temporary files directory on Destination.'), LogLevel::OK);
$return = drush_invoke_process($destination, 'core-status', array(), array(), array('integrate' => FALSE, 'override-simulated' => TRUE));
if (!$return['error_status'] && isset($return['object']['drush-temp'])) {
$tmp = $return['object']['drush-temp'];
}
$destination_dump_path = Path::join($tmp, basename($source_dump_path));
$rsync_options['yes'] = TRUE; // No need to prompt as destination is a tmp file.
}
if ($do_rsync) {
if (!drush_get_option('no-dump')) {
// Cleanup if this command created the dump file.
$rsync_options['remove-source-files'] = TRUE;
}
$runner = drush_get_runner($source_settings, $destination_settings, drush_get_option('runner', FALSE));
// Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync.
// Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync).
$return = drush_invoke_process($runner, 'core-rsync', array("$source:$source_dump_path", "$destination:$destination_dump_path"), $rsync_options);
drush_log(dt('Copying dump file from Source to Destination.'), LogLevel::OK);
if ($return['error_status']) {
return drush_set_error('DRUSH_RSYNC_FAILED', dt('core-rsync failed.'));
}
}
// Import file into destination.
drush_log(dt('Starting to import dump file onto Destination database.'), LogLevel::OK);
$options = $global_options + $target_options + array(
'file' => $destination_dump_path,
'file-delete' => TRUE,
);
$return = drush_invoke_process($destination, 'sql-query', array(), $options, $backend_options);
if ($return['error_status']) {
// An error was already logged.
return FALSE;
}
// Run Sanitize if needed.
$options = $global_options + $target_options;
if (drush_get_option('sanitize')) {
drush_log(dt('Starting to sanitize target database on Destination.'), LogLevel::OK);
$return = drush_invoke_process($destination, 'sql-sanitize', array(), $options, $backend_options);
if ($return['error_status']) {
return drush_set_error('DRUSH_SQL_SANITIZE_FAILED', dt('sql-sanitize failed.'));
}
}
}

View file

@ -0,0 +1,420 @@
<?php
use Drush\Log\LogLevel;
use Drush\User\UserList;
use Drush\User\UserListException;
/**
* @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() {
$options_common = array(
'uid' => array(
'description' => 'A comma delimited list of uids of users to operate on.',
'example-value' => '3,5',
'value' => 'required',
),
'name' => array(
'description' => 'A comma delimited list of user names of users to operate on.',
'example-value' => 'foo',
'value' => 'required',
),
'mail' => array(
'description' => 'A comma delimited list of user mail addresses of users to operate on.',
'example-value' => 'me@example.com',
'value' => 'required',
)
);
$items['user-information'] = array(
'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 the listed users.',
),
'arguments' => array(
'users' => 'A comma delimited list of uids, user names, or email addresses.',
),
'required-arguments' => TRUE,
'outputformat' => array(
'default' => 'key-value-list',
'pipe-format' => 'csv',
'field-labels' => array(
'uid' => 'User ID',
'name' => 'User name',
'pass' => 'Password',
'mail' => 'User mail',
'theme' => 'User theme',
'signature' => 'Signature',
'signature_format' => 'Signature format',
'user_created' => 'User created',
'created' => 'Created',
'user_access' => 'User last access',
'access' => 'Last access',
'user_login' => 'User last login',
'login' => 'Last login',
'user_status' => 'User status',
'status' => 'Status',
'timezone' => 'Time zone',
'picture' => 'User picture',
'init' => 'Initial user mail',
'roles' => 'User roles',
'group_audience' => 'Group Audience',
'langcode' => 'Language code',
'uuid' => 'Uuid',
),
'format-cell' => 'csv',
'fields-default' => array('uid', 'name', 'mail', 'roles', 'user_status'),
'fields-pipe' => array('name', 'uid', 'mail', 'status', 'roles'),
'fields-full' => array('uid', 'name', 'pass', 'mail', 'theme', 'signature', 'user_created', 'user_access', 'user_login', 'user_status', 'timezone', 'roles', 'group_audience', 'langcode', 'uuid'),
'output-data-type' => 'format-table',
),
);
$items['user-block'] = array(
'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' => $options_common,
);
$items['user-unblock'] = array(
'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' => $options_common,
);
$items['user-add-role'] = array(
'description' => 'Add a role to the specified user accounts.',
'aliases' => array('urol'),
'arguments' => array(
'role' => 'The name of the role to add',
'users' => '(optional) A comma delimited list of uids, user names, or email addresses.',
),
'required-arguments' => 1,
'examples' => array(
'drush user-add-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
'Add the "power user" role to the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
),
'options' => $options_common,
);
$items['user-remove-role'] = array(
'description' => 'Remove a role from the specified user accounts.',
'aliases' => array('urrol'),
'arguments' => array(
'role' => 'The name of the role to remove',
'users' => '(optional) A comma delimited list of uids, user names, or email addresses.',
),
'required-arguments' => 1,
'examples' => array(
'drush user-remove-role "power user" 5,user3 --uid=2,3 --name=someguy,somegal --mail=billgates@microsoft.com' =>
'Remove the "power user" role from the accounts with name, id, or email 5 or user3, uids 2 and 3, names someguy and somegal, and email address of billgates@microsoft.com',
),
'options' => $options_common,
);
$items['user-create'] = array(
'description' => 'Create a user account with the specified name.',
'aliases' => array('ucrt'),
'arguments' => array(
'name' => 'The name of the account to add'
),
'required-arguments' => TRUE,
'examples' => array(
'drush user-create newuser --mail="person@example.com" --password="letmein"' =>
'Create a new user account with the name newuser, the email address person@example.com, and the password letmein',
),
'options' => array(
'password' => 'The password for the new account',
'mail' => 'The email address for the new account',
),
'outputformat' => $items['user-information']['outputformat'],
);
$items['user-cancel'] = array(
'description' => 'Cancel a user account with the specified name.',
'aliases' => array('ucan'),
'arguments' => array(
'name' => 'The name of the account to cancel',
),
// The `_user_cancel` method still references global $user.
// @todo remove once https://www.drupal.org/node/2163205 is in.
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_LOGIN,
'required-arguments' => TRUE,
'examples' => array(
'drush user-cancel username' =>
'Cancel the user account with the name username and anonymize all content created by that user.',
),
);
$items['user-password'] = array(
'description' => '(Re)Set the password for the user account with the specified name.',
'aliases' => array('upwd'),
'arguments' => array(
'name' => 'The name of the account to modify.'
),
'required-arguments' => TRUE,
'options' => array(
'password' => array(
'description' => 'The new password for the account.',
'required' => TRUE,
'example-value' => 'foo',
),
),
'examples' => array(
'drush user-password someuser --password="correct horse battery staple"' =>
'Set the password for the username someuser. @see xkcd.com/936',
),
);
$items['user-login'] = array(
'description' => 'Display a one time login link for the given user account (defaults to uid 1).',
'aliases' => array('uli'),
'bootstrap' => DRUSH_BOOTSTRAP_NONE,
'handle-remote-commands' => TRUE,
'arguments' => array(
'user' => 'An optional uid, user name, or email address for the user to log in as. Default is to log in as uid 1. The uid/name/mail options take priority if specified.',
'path' => 'Optional path to redirect to after logging in.',
),
'options' => array(
'browser' => 'Optional value denotes which browser to use (defaults to operating system default). Use --no-browser to suppress opening a browser.',
'uid' => 'A uid to log in as.',
'redirect-port' => 'A custom port for redirecting to (e.g. when running within a Vagrant environment)',
'name' => 'A user name to log in as.',
'mail' => 'A user mail address to log in as.',
),
'examples' => array(
'drush user-login ryan node/add/blog' => 'Displays and opens default web browser (if configured or detected) for a one-time login link for the user with the username ryan and redirect to the path node/add/blog.',
'drush user-login --browser=firefox --mail=drush@example.org admin/settings/performance' => 'Open firefox web browser, login as the user with the e-mail address drush@example.org and redirect to the path admin/settings/performance.',
),
);
return $items;
}
/**
* Implements hook_drush_help_alter().
*/
function user_drush_help_alter(&$command) {
// Drupal 7+ only options.
if ($command['command'] == 'user-cancel' && drush_drupal_major_version() >= 7) {
$command['options']['delete-content'] = 'Delete all content created by the user';
$command['examples']['drush user-cancel --delete-content username'] =
'Cancel the user account with the name username and delete all content created by that user.';
}
}
/**
* Get a version specific UserSingle class.
*
* @param $account
* @return \Drush\User\UserSingleBase
*
* @see drush_get_class().
*/
function drush_usersingle_get_class($account) {
return drush_get_class('Drush\User\UserSingle', array($account));
}
/**
* Get a version specific User class.
*
* @return \Drush\User\UserVersion
*
* @see drush_get_class().
*/
function drush_user_get_class() {
return drush_get_class('Drush\User\User');
}
/**
* Command callback. Prints information about the specified user(s).
*/
function drush_user_information($users) {
$userlist = new UserList($users);
$info = $userlist->each('info');
return $info;
}
/**
* Block the specified user(s).
*/
function drush_user_block($users = '') {
$userlist = new UserList($users);
$userlist->each('block');
drush_log(dt('Blocked user(s): !users', array('!users' => $userlist->names())), LogLevel::SUCCESS);
}
/**
* Unblock the specified user(s).
*/
function drush_user_unblock($users = '') {
$userlist = new UserList($users);
$userlist->each('unblock');
drush_log(dt('Unblocked user(s): !users', array('!users' => $userlist->names())), LogLevel::SUCCESS);
}
/**
* Add a role to the specified user accounts.
*/
function drush_user_add_role($role, $users = '') {
// If role is not found, an exception gets thrown and handled by command invoke.
$role_object = drush_role_get_class($role);
$userlist = new UserList($users);
$userlist->each('addRole', array($role_object->rid));
drush_log(dt('Added role !role role to !users', array('!role' => $role, '!users' => $userlist->names())),LogLevel::SUCCESS);
}
/**
* Remove a role from the specified user accounts.
*/
function drush_user_remove_role($role, $users = '') {
// If role is not found, an exception gets thrown and handled by command invoke.
$role_object = drush_role_get_class($role);
$userlist = new UserList($users);
$userlist->each('removeRole', array($role_object->rid));
drush_log(dt('Removed !role role from !users', array('!role' => $role, '!users' => $userlist->names())),LogLevel::SUCCESS);
}
/**
* Creates a new user account.
*/
function drush_user_create($name) {
$userversion = drush_user_get_class();
$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_get_context('DRUSH_SIMULATE')) {
if ($account = $userversion->create($new_user)) {
return array($account->id() => $account->info());
}
else {
return drush_set_error("Could not create a new user account with the name " . $name . ".");
}
}
}
function drush_user_create_validate($name) {
$userversion = drush_user_get_class();
if ($mail = drush_get_option('mail')) {
if ($userversion->load_by_mail($mail)) {
return drush_set_error(dt('There is already a user account with the email !mail', array('!mail' => $mail)));
}
}
if ($userversion->load_by_name($name)) {
return drush_set_error(dt('There is already a user account with the name !name', array('!name' => $name)));
}
}
/**
* Cancels a user account.
*/
function drush_user_cancel($inputs) {
$userlist = new UserList($inputs);
foreach ($userlist->accounts as $account) {
if (drush_get_option('delete-content') && drush_drupal_major_version() >= 7) {
drush_print(dt('All content created by !name will be deleted.', array('!name' => $account->getUsername())));
}
if (drush_confirm('Cancel user account?: ')) {
$account->cancel();
}
}
drush_log(dt('Cancelled user(s): !users', array('!users' => $userlist->names())),LogLevel::SUCCESS);
}
/**
* Sets the password for the account with the given username
*/
function drush_user_password($inputs) {
$userlist = new UserList($inputs);
if (!drush_get_context('DRUSH_SIMULATE')) {
$pass = drush_get_option('password');
// If no password has been provided, prompt for one.
if (empty($pass)) {
$pass = drush_prompt(dt('Password'), NULL, TRUE, TRUE);
}
foreach ($userlist->accounts as $account) {
$userlist->each('password', array($pass));
}
drush_log(dt('Changed password for !users', array('!users' => $userlist->names())), LogLevel::SUCCESS);
}
}
/**
* Displays a one time login link for the given user.
*/
function drush_user_login($inputs = '', $path = NULL) {
$args = func_get_args();
// Redispatch if called against a remote-host so a browser is started on the
// the *local* machine.
$alias = drush_get_context('DRUSH_TARGET_SITE_ALIAS');
if (drush_sitealias_is_remote_site($alias)) {
$return = drush_invoke_process($alias, 'user-login', $args, drush_redispatch_get_options(), array('integrate' => FALSE));
if ($return['error_status']) {
return drush_set_error('Unable to execute user login.');
}
else {
// Prior versions of Drupal returned a string so cast to an array if needed.
$links = is_string($return['object']) ? array($return['object']) : $return['object'];
}
}
else {
if (!drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
// Fail gracefully if unable to bootstrap Drupal.
// drush_bootstrap() has already logged an error.
return FALSE;
}
if (drush_get_option('uid', FALSE) || drush_get_option('name', FALSE) || drush_get_option('mail', FALSE)) {
// If we only have a single argument and one of the user options is passed,
// then we assume the argument is the path to open.
if (count($args) == 1) {
$path = $args[0];
}
}
// Try to load a user from provided options and arguments.
try {
$userlist = new UserList($inputs);
}
catch (UserListException $e) {
// No user option or argument was passed, so we default to uid 1.
$userlist = new UserList(1);
}
$links = $userlist->each('passResetUrl', array($path));
}
$port = drush_get_option('redirect-port', FALSE);
// There is almost always only one link so pick the first one for display and browser.
// The full array is sent on backend calls.
$first = current($links);
drush_start_browser($first, FALSE, $port);
drush_backend_set_result($links);
return $first;
}

103
vendor/drush/drush/commands/xh.drush.inc vendored Normal file
View file

@ -0,0 +1,103 @@
<?php
/**
* @file
*/
use Drush\Log\LogLevel;
define('XH_PROFILE_MEMORY', FALSE);
define('XH_PROFILE_CPU', FALSE);
define('XH_PROFILE_BUILTINS', TRUE);
/**
* Implements hook_drush_help_alter().
*/
function xh_drush_help_alter(&$command) {
if ($command['command'] == 'global-options') {
// Do not include these in options in standard help.
if ($command['#brief'] === FALSE) {
$command['options']['xh'] = array(
'description' => 'Enable profiling via XHProf',
);
$command['sub-options']['xh']['xh-link'] = 'URL to your XHProf report site.';
$command['sub-options']['xh']['xh-path'] = 'Absolute path to the xhprof project.';
$command['sub-options']['xh']['xh-profile-builtins'] = 'Profile built-in PHP functions (defaults to TRUE).';
$command['sub-options']['xh']['xh-profile-cpu'] = 'Profile CPU (defaults to FALSE).';
$command['sub-options']['xh']['xh-profile-memory'] = 'Profile Memory (defaults to FALSE).';
}
}
}
function xh_enabled() {
if (drush_get_option('xh')) {
if (!extension_loaded('xhprof')) {
return drush_set_error('You must enable the xhprof PHP extension in your CLI PHP in order to profile.');
}
if (!drush_get_option('xh-path', '')) {
return drush_set_error('You must provide the xh-path option in your drushrc or on the CLI in order to profile.');
}
return TRUE;
}
}
/**
* Determines flags for xhprof_enable based on drush options.
*/
function xh_flags() {
$flags = 0;
if (!drush_get_option('xh-profile-builtins', XH_PROFILE_BUILTINS)) {
$flags |= XHPROF_FLAGS_NO_BUILTINS;
}
if (drush_get_option('xh-profile-cpu', XH_PROFILE_CPU)) {
$flags |= XHPROF_FLAGS_CPU;
}
if (drush_get_option('xh-profile-memory', XH_PROFILE_MEMORY)) {
$flags |= XHPROF_FLAGS_MEMORY;
}
return $flags;
}
/**
* Implements hook_drush_init().
*/
function xh_drush_init() {
if (xh_enabled()) {
if ($path = drush_get_option('xh-path', '')) {
include_once $path . '/xhprof_lib/utils/xhprof_lib.php';
include_once $path . '/xhprof_lib/utils/xhprof_runs.php';
xhprof_enable(xh_flags());
}
}
}
/**
* Implements hook_drush_exit().
*/
function xh_drush_exit() {
if (xh_enabled()) {
$args = func_get_args();
$namespace = 'Drush'; // variable_get('site_name', '');
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $namespace);
if ($url = xh_link($run_id)) {
drush_log(dt('XHProf run saved. View report at !url', array('!url' => $url)), LogLevel::OK);
}
}
}
/**
* Returns the XHProf link.
*/
function xh_link($run_id) {
if ($xhprof_url = trim(drush_get_option('xh-link'))) {
$namespace = 'Drush'; //variable_get('site_name', '');
return $xhprof_url . '/index.php?run=' . urlencode($run_id) . '&source=' . urlencode($namespace);
}
else {
drush_log('Configure xh_link in order to see a link to the XHProf report for this request instead of this message.');
}
}

84
vendor/drush/drush/composer.json vendored Normal file
View file

@ -0,0 +1,84 @@
{
"name": "drush/drush",
"description": "Drush is a command line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those of us who spend some of our working hours hacking away at the command prompt.",
"homepage": "http://www.drush.org",
"license": "GPL-2.0+",
"minimum-stability": "stable",
"prefer-stable": true,
"authors": [
{ "name": "Moshe Weitzman", "email": "weitzman@tejasa.com" },
{ "name": "Owen Barton", "email": "drupal@owenbarton.com" },
{ "name": "Mark Sonnabaum", "email": "marksonnabaum@gmail.com" },
{ "name": "Antoine Beaupré", "email": "anarcat@koumbit.org" },
{ "name": "Greg Anderson", "email": "greg.1.anderson@greenknowe.org" },
{ "name": "Jonathan Araña Cruz", "email": "jonhattan@faita.net" },
{ "name": "Jonathan Hedstrom", "email": "jhedstrom@gmail.com" },
{ "name": "Christopher Gervais", "email": "chris@ergonlogic.com" },
{ "name": "Dave Reid", "email": "dave@davereid.net" },
{ "name": "Damian Lee", "email": "damiankloip@googlemail.com" }
],
"support": {
"forum": "http://drupal.stackexchange.com/questions/tagged/drush",
"irc": "irc://irc.freenode.org/drush"
},
"bin": [
"drush",
"drush.launcher",
"drush.php",
"drush.complete.sh"
],
"config": {
"platform": {
"php": "5.4.5"
}
},
"require": {
"php": ">=5.4.5",
"psr/log": "~1.0",
"psy/psysh": "~0.6",
"consolidation/annotated-command": "~2",
"consolidation/output-formatters": "~3",
"symfony/yaml": "~2.3|^3",
"symfony/var-dumper": "~2.7|^3",
"symfony/console": "~2.7|^3",
"symfony/event-dispatcher": "~2.7|^3",
"symfony/finder": "~2.7|^3",
"pear/console_table": "~1.3.0",
"phpdocumentor/reflection-docblock": "^2.0",
"webmozart/path-util": "~2"
},
"require-dev": {
"symfony/var-dumper": "~2.7",
"symfony/console": "~2.7",
"symfony/event-dispatcher": "~2.7",
"symfony/finder": "~2.7",
"symfony/yaml": "~2.3",
"phpunit/phpunit": "4.*",
"symfony/process": "2.7.*"
},
"suggest": {
"ext-pcntl": "*",
"drush/config-extra": "Provides configuration workflow commands, such as config-merge."
},
"autoload": {
"psr-0": {
"Drush": "lib/",
"Consolidation": "lib/"
}
},
"autoload-dev": {
"psr-0": {
"Unish": "tests/"
}
},
"scripts": {
"lint": [
"find includes -name '*.inc' -print0 | xargs -0 -n1 php -l"
]
},
"extra": {
"branch-alias": {
"dev-master": "8.0.x-dev"
}
}
}

1933
vendor/drush/drush/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

64
vendor/drush/drush/docs/bastion.md vendored Normal file
View file

@ -0,0 +1,64 @@
# Remote Operations on Drupal Sites via a Bastion Server
Wikipedia defines a [bastion server](http://en.wikipedia.org/wiki/Bastion_host) as "a special purpose computer on a network specifically designed and configured to withstand attacks." For the purposes of this documentation, though, any server that you can ssh through to reach other servers will do. Using standard ssh and Drush techniques, it is possible to make a two-hop remote command look and act as if the destination machine is on the same network as the source machine.
## Recap of Remote Site Aliases
Site aliases can refer to Drupal sites that are running on remote machines simply including 'remote-host' and 'remote-user' attributes:
$aliases['internal'] = array(
'remote-host' => 'internal.company.com',
'remote-user' => 'wwwadmin',
'uri' => 'http://internal.company.com',
'root' => '/path/to/remote/drupal/root',
);
With this alias defintion, you may use commands such as `drush @internal status`, `drush ssh @internal` and `drush rsync @internal @dev` to operate remotely on the internal machine. What if you cannot reach the server that site is on from your current network? Enter the bastion server.
## Setting up a Bastion server in .ssh/config
If you have access to a server, bastion.company.com, which you can ssh to from the open internet, and if the bastion server can in turn reach the internal server, then it is possible to configure ssh to route all traffic to the internal server through the bastion. The .ssh configuration file would look something like this:
In **.ssh/config:**
Host internal.company.com
ProxyCommand ssh user@bastion.company.com nc %h %p
That is all that is necessary; however, if the dev machine you are configuring is a laptop that might sometimes be inside the company intranet, you might want to optimize this setup so that the bastion is not used when the internal server can be reached directly. You could do this by changing the contents of your .ssh/config file when your network settings change -- or you could use Drush.
# Setting up a Bastion server via Drush configuration
First, make sure that you do not have any configuration options for the internal machine in your .ssh/config file. The next step after that is to identify when you are connected to your company intranet. I like to determine this by using the `route` command to find my network gateway address, since this is always the same on my intranet, and unlikely to be encountered in other places.
In **drushrc.php:**
# Figure out if we are inside our company intranet by testing our gateway address against a known value
exec("route -n | grep '^0\.0\.0\.0' | awk '{ print $2; }' 2> /dev/null", $output);
if ($output[0] == '172.30.10.1') {
drush_set_context('MY_INTRANET', TRUE);
}
After this code runs, the 'MY\_INTRANET' context will be set if our gateway IP address matches the expected value, and unset otherwise. We can make use of this in our alias files.
In **aliases.drushrc.php:**
if (drush_get_context('MY_INTRANET', FALSE) === FALSE) {
$aliases['intranet-proxy'] = array(
'ssh-options' => ' -o "ProxyCommand ssh user@bastion.company.com nc %h %p"',
);
}
$aliases['internal-server'] = array(
'parent' => '@intranet-proxy',
'remote-host' => 'internal.company.com',
'remote-user' => 'wwwadmin',
);
$aliases['internal'] = array(
'parent' => '@internal-server',
'uri' => 'http://internal.company.com',
'root' => '/path/to/remote/drupal/root',
);
The 'parent' term of the internal-server alias record is ignored if the alias it references ('@intranet-proxy') is not defined; the result is that 'ssh-options' will only be defined when outside of the intranet, and the ssh ProxyCommand to the bastion server will only be included when it is needed.
With this setup, you will be able to use your site alias '@internal' to remotely operate on your internal intranet Drupal site seemlessly, regardless of your location -- a handy trick indeed.

38
vendor/drush/drush/docs/bootstrap.md vendored Normal file
View file

@ -0,0 +1,38 @@
The Drush Bootstrap Process
===========================
When preparing to run a command, Drush works by "bootstrapping" the Drupal environment in very much the same way that is done during a normal page request from the web server, so most Drush commands run in the context of a fully-initialized website.
For efficiency and convenience, some Drush commands can work without first bootstrapping a Drupal site, or by only partially bootstrapping a site. This is faster than a full bootstrap. It is also a matter of convenience, because some commands are useful even when you don't have a working Drupal site. For example, you can use Drush to download Drupal with `drush dl drupal`. This obviously does not require any bootstrapping to work.
DRUSH\_BOOTSTRAP\_NONE
-----------------------
Only run Drush _preflight_, without considering Drupal at all. Any code that operates on the Drush installation, and not specifically any Drupal directory, should bootstrap to this phase.
DRUSH\_BOOTSTRAP\_DRUPAL\_ROOT
------------------------------
Set up and test for a valid Drupal root, either through the --root options, or evaluated based on the current working directory. Any code that interacts with an entire Drupal installation, and not a specific site on the Drupal installation should use this bootstrap phase.
DRUSH\_BOOTSTRAP\_DRUPAL\_SITE
------------------------------
Set up a Drupal site directory and the correct environment variables to allow Drupal to find the configuration file. If no site is specified with the --uri options, Drush will assume the site is 'default', which mimics Drupal's behaviour. Note that it is necessary to specify a full URI, e.g. --uri=http://example.com, in order for certain Drush commands and Drupal modules to behave correctly. See the [example Config file](../examples/example.drushrc.php) for more information. Any code that needs to modify or interact with a specific Drupal site's settings.php file should bootstrap to this phase.
DRUSH\_BOOTSTRAP\_DRUPAL\_CONFIGURATION
---------------------------------------
Load the settings from the Drupal sites directory. This phase is analagous to the DRUPAL\_BOOTSTRAP\_CONFIGURATION bootstrap phase in Drupal itself, and this is also the first step where Drupal specific code is included. This phase is commonly used for code that interacts with the Drupal install API, as both install.php and update.php start at this phase.
DRUSH\_BOOTSTRAP\_DRUPAL\_DATABASE
----------------------------------
Connect to the Drupal database using the database credentials loaded during the previous bootstrap phase. This phase is analogous to the DRUPAL\_BOOTSTRAP\_DATABASE bootstrap phase in Drupal. Any code that needs to interact with the Drupal database API needs to be bootstrapped to at least this phase.
DRUSH\_BOOTSTRAP\_DRUPAL\_FULL
------------------------------
Fully initialize Drupal. This is analogous to the DRUPAL\_BOOTSTRAP\_FULL bootstrap phase in Drupal. Any code that interacts with the general Drupal API should be bootstrapped to this phase.
DRUSH\_BOOTSTRAP\_DRUPAL\_LOGIN
-------------------------------
Log in to the initialiazed Drupal site. This bootstrap phase is used after the site has been fully bootstrapped. This is the default bootstrap phase all commands will try to reach, unless otherwise specified. This phase will log you in to the drupal site with the username or user ID specified by the --user/ -u option(defaults to 0, anonymous). Use this bootstrap phase for your command if you need to have access to information for a specific user, such as listing nodes that might be different based on who is logged in.
DRUSH\_BOOTSTRAP\_MAX
---------------------
This is not an actual bootstrap phase. Commands that use DRUSH\_BOOTSTRAP\_MAX will cause Drush to bootstrap as far as possible, and then run the command regardless of the bootstrap phase that was reached. This is useful for Drush commands that work without a bootstrapped site, but that provide additional information or capabilities in the presence of a bootstrapped site. For example, `drush pm-releases modulename` works without a bootstrapped Drupal site, but will include the version number for the installed module if a Drupal site has been bootstrapped.

132
vendor/drush/drush/docs/commands.md vendored Normal file
View file

@ -0,0 +1,132 @@
Creating Custom Drush Commands
==============================
Creating a new Drush command is very easy. Follow these simple steps:
1. Create a command file called COMMANDFILE.drush.inc
1. Implement the function COMMANDFILE\_drush\_command()
1. Implement the functions that your commands will call. These will usually be named drush\_COMMANDFILE\_COMMANDNAME().
For an example Drush command, see examples/sandwich.drush.inc. The steps for implementing your command are explained in more detail below.
Create COMMANDFILE.drush.inc
----------------------------
The name of your Drush command is very important. It must end in ".drush.inc" to be recognized as a Drush command. The part of the filename that comes before the ".drush.inc" becomes the name of the commandfile. Optionally, the commandfile may be restricted to a particular version of Drupal by adding a ".dVERSION" after the name of the commandfile (e.g. ".d8.drush.inc") Your commandfile name is used by Drush to compose the names of the functions it will call, so choose wisely.
The example Drush command, 'make-me-a-sandwich', is stored in the 'sandwich' commandfile, 'sandwich.Drush.inc'. You can find this file in the 'examples' directory in the Drush distribution.
Drush searches for commandfiles in the following locations:
- Folders listed in the 'include' option (see `drush topic docs-configuration`).
- The system-wide Drush commands folder, e.g. /usr/share/drush/commands
- The ".drush" folder in the user's HOME folder.
- /drush and /sites/all/drush in the current Drupal installation
- All enabled modules in the current Drupal installation
- Folders and files containing other versions of Drush in their names will be \*skipped\* (e.g. devel.drush4.inc or drush4/devel.drush.inc). Names containing the current version of Drush (e.g. devel.drush5.inc) will be loaded.
Note that modules in the current Drupal installation will only be considered if Drush has bootstrapped to at least the DRUSH\_BOOSTRAP\_SITE level. Usually, when working with a Drupal site, Drush will bootstrap to DRUSH\_BOOTSTRAP\_FULL; in this case, only the Drush commandfiles in enabled modules will be considered eligible for loading. If Drush only bootstraps to DRUSH\_BOOTSTRAP\_SITE, though, then all Drush commandfiles will be considered, whether the module is enabled or not. See `drush topic docs-bootstrap` for more information on bootstrapping.
Implement COMMANDFILE\_drush\_command()
---------------------------------------
The drush\_command hook is the most important part of the commandfile. It returns an array of items that define how your commands should be called, and how they work. Drush commands are very similar to the Drupal menu system. The elements that can appear in a Drush command definition are shown below.
- **aliases**: Provides a list of shorter names for the command. For example, pm-download may also be called via `drush dl`. If the alias is used, Drush will substitute back in the primary command name, so pm-download will still be used to generate the command hook, etc.
- **command-hook**: Change the name of the function Drush will call to execute the command from drush\_COMMANDFILE\_COMMANDNAME() to drush\_COMMANDFILE\_COMMANDHOOK(), where COMMANDNAME is the original name of the command, and COMMANDHOOK is the value of the 'command-hook' item.
- **callback**: Name of function to invoke for this command. The callback function name \_must\_ begin with "drush\_commandfile\_", where commandfile is from the file "commandfile.drush.inc", which contains the commandfile\_drush\_command() function that returned this command. Note that the callback entry is optional; it is preferable to omit it, in which case drush\_invoke() will generate the hook function name.
- **callback arguments**: An array of arguments to pass to the callback. The command line arguments, if any, will appear after the callback arguments in the function parameters.
- **description**: Description of the command.
- **arguments**: An array of arguments that are understood by the command. Used by `drush help` only.
- **required-arguments**: Defaults to FALSE; TRUE if all of the arguments are required. Set to an integer count of required arguments if only some are required.
- **options**: An array of options that are understood by the command. Any option that the command expects to be able to query via drush\_get\_option \_must\_ be listed in the options array. If it is not, users will get an error about an "Unknown option" when they try to specify the option on the command line.
The value of each option may be either a simple string containing the option description, or an array containing the following information:
- **description**: A description of the option.
- **example-value**: An example value to show in help.
- **value**: optional|required.
- **required**: Indicates that an option must be provided.
- **hidden**: The option is not shown in the help output (rare).
- **allow-additional-options**: If TRUE, then the strict validation to see if options exist is skipped. Examples of where this is done includes the core-rsync command, which passes options along to the rsync shell command. This item may also contain a list of other commands that are invoked as subcommands (e.g. the pm-update command calls pm-updatecode and updatedb commands). When this is done, the options from the subcommand may be used on the commandline, and are also listed in the command's `help` output. Defaults to FALSE.
- **examples**: An array of examples that are understood by the command. Used by `drush help` only.
- **scope**: One of 'system', 'project', 'site'. Not currently used.
- **bootstrap**: Drupal bootstrap level. More info at `drush topic docs-bootstrap`. Valid values are:
- DRUSH\_BOOTSTRAP\_NONE
- DRUSH\_BOOTSTRAP\_DRUPAL\_ROOT
- DRUSH\_BOOTSTRAP\_DRUPAL\_SITE
- DRUSH\_BOOTSTRAP\_DRUPAL\_CONFIGURATION
- DRUSH\_BOOTSTRAP\_DRUPAL\_DATABASE
- DRUSH\_BOOTSTRAP\_DRUPAL\_FULL
- DRUSH\_BOOTSTRAP\_DRUPAL\_LOGIN (default)
- DRUSH\_BOOTSTRAP\_MAX
- **core**: Drupal major version required. Append a '+' to indicate 'and later versions.'
- **drupal dependencies**: Drupal modules required for this command.
- **drush dependencies**: Other Drush commandfiles required for this command.
- **engines**: Provides a list of Drush engines to load with this command. The set of appropriate engines varies by command.
- **outputformat**: One important engine is the 'outputformat' engine. This engine is responsible for formatting the structured data (usually an associative array) that a Drush command returns as its function result into a human-readable or machine-parsable string. Some of the options that may be used with output format engines are listed below; however, each specific output format type can take additional option items that control the way that the output is rendered. See the comment in the output format's implementation for information. The Drush core output format engines can be found in commands/core/outputformat.
- **default**: The default type to render output as. If declared, the command should not print any output on its own, but instead should return a data structure (usually an associative array) that can be rendered by the output type selected.
- **pipe-format**: When the command is executed in --pipe mode, the command output will be rendered by the format specified by the pipe-format item instead of the default format. Note that in either event, the user may specify the format to use via the --format command-line option.
- **formatted-filter** and **pipe-filter**: Specifies a function callback that will be used to filter the command result. The filter is selected based on the type of output format object selected. Most output formatters are 'pipe' formatters, that produce machine-parsable output. A few formatters, such as 'table' and 'key-value' are 'formatted' filter types, that produce human-readable output.
- **topics**: Provides a list of topic commands that are related in some way to this command. Used by `drush help`.
- **topic**: Set to TRUE if this command is a topic, callable from the `drush docs-topics` command.
- **category**: Set this to override the category in which your command is listed in help.
The 'sandwich' drush\_command hook looks like this:
function sandwich_drush_command() {
$items = array();
$items['make-me-a-sandwich'] = array(
'description' => "Makes a delicious sandwich.",
'arguments' => array(
'filling' => 'The type of the sandwich (turkey, cheese, etc.)',
),
'options' => array(
'spreads' => 'Comma delimited list of spreads (e.g. mayonnaise, mustard)',
),
'examples' => array(
'drush mmas turkey --spreads=ketchup,mustard' => 'Make a terrible-tasting sandwich that is lacking in pickles.',
),
'aliases' => array('mmas'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUSH, // No bootstrap at all.
);
return $items;
}
Most of the items in the 'make-me-a-sandwich' command definition have no effect on execution, and are used only by `drush help`. The exceptions are 'aliases' (described above) and 'bootstrap'. As previously mentioned, `drush topic docs-bootstrap` explains the Drush bootstrapping process in detail.
Implement drush\_COMMANDFILE\_COMMANDNAME()
-------------------------------------------
The 'make-me-a-sandwich' command in sandwich.drush.inc is defined as follows:
function drush_sandwich_make_me_a_sandwich($filling = 'ascii') {
// implementation here ...
}
If a user runs `drush make-me-a-sandwich` with no command line arguments, then Drush will call drush\_sandwich\_make\_me\_a\_sandwich() with no function parameters; in this case, $filling will take on the provided default value, 'ascii'. (If there is no default value provided, then the variable will be NULL, and a warning will be printed.) Running `drush make-me-a-sandwich ham` will cause Drush to call drush\_sandwich\_make\_me\_a\_sandwich('ham'). In the same way, commands that take two command line arguments can simply define two functional parameters, and a command that takes a variable number of command line arguments can use the standard php function func\_get\_args() to get them all in an array for easy processing.
It is also very easy to query the command options using the function drush\_get\_option(). For example, in the drush\_sandwich\_make\_me\_a\_sandwich() function, the --spreads option is retrieved as follows:
$str_spreads = '';
if ($spreads = drush_get_option('spreads')) {
$list = implode(' and ', explode(',', $spreads));
$str_spreads = ' with just a dash of ' . $list;
}
Note that Drush will actually call a sequence of functions before and after your Drush command function. One of these hooks is the "validate" hook. The 'sandwich' commandfile provides a validate hook for the 'make-me-a-sandwich' command:
function drush_sandwich_make_me_a_sandwich_validate() {
$name = posix_getpwuid(posix_geteuid());
if ($name['name'] !== 'root') {
return drush_set_error('MAKE_IT_YOUSELF', dt('What? Make your own sandwich.'));
}
}
The validate function should call drush\_set\_error() and return its result if the command cannot be validated for some reason. See `drush topic docs-policy` for more information on defining policy functions with validate hooks, and `drush topic docs-api` for information on how the command hook process works. Also, the list of defined drush error codes can be found in `drush topic docs-errorcodes`.
To see the full implementation of the sample 'make-me-a-sandwich' command, see `drush topic docs-examplecommand`.

Some files were not shown because too many files have changed in this diff Show more