type === 'site') {
if (drush_get_option('installed')) {
// Don't generate the drushrc.php on provision-save/delete commands.
if (!preg_match("/^provision-(save|delete)/", $command[0])) {
provision_save_site_data();
}
}
}
elseif (d()->type === 'platform') {
// Don't generate the drushrc.php on provision-save/delete commands.
if (!preg_match("/^provision-(save|delete)/", $command[0])) {
provision_save_platform_data();
}
}
}
}
/**
* Test to see if the site settings.php exists
*
* @param url
* The url of the site to check
* @return
* If the file exists, return TRUE, else return FALSE.
*/
function _provision_drupal_site_exists() {
return file_exists(d()->site_path . '/settings.php');
}
/**
* This command does the actual installation in it's own thread,
* so we can recover gracefully if things go really wrong.
*/
function drush_provision_drupal_provision_install_backend() {
drush_include_engine('drupal', 'install');
}
/**
* Sync the current Drupal platform and, if applicable, site. Call after
* finishing operations that affect the filesystem.
*
* @param boolean $override_slave_authority
* Overwrite e.g. the files directory on the slave.
* Useful when the master server has donw file operations, such as restoring a backup.
*/
function provision_drupal_push_site($override_slave_authority = FALSE) {
provision_file()->create_dir(d()->server->http_platforms_path, dt("Platforms"), 0755);
d()->server->sync(d()->server->http_platforms_path, array(
'exclude' => d()->server->http_platforms_path . '/*', // Make sure remote directory is created
));
// Sync the platform
d()->service('http')->sync(d()->root, array('exclude-sites' => TRUE));
if (d()->type === 'site') {
$options = array();
if ($override_slave_authority) {
$exclude = NULL;
}
else {
$exclude = 'files/*' . PATH_SEPARATOR . 'private/*';
}
// Store the current exclude-path option
$old_exclude = NULL;
if(!is_null($exclude)) {
$old_exclude = drush_get_option('exclude-paths');
drush_set_option('exclude-paths', $exclude);
}
// Sync all filesystem changes to the remote server.
d()->service('http')->sync(d()->site_path, $options);
// Reset the exclude-path option
if(!is_null($exclude)) {
if(empty($old_exclude)) {
drush_unset_option('exclude-paths');
} else {
drush_set_option('exclude-paths', $old_exclude);
}
}
}
}
/**
* Sync the current Drupal site BACK from a slave. Call before
* running operations that need files where the slave is authoritative.
*
* E.g. before a backup is made.
*/
function provision_drupal_fetch_site() {
// synch filesystem changes back from the remote server.
d()->service('http')->fetch(d()->site_path . '/files/');
d()->service('http')->fetch(d()->site_path . '/private/');
d()->service('http')->fetch(d()->site_path . '/modules/');
d()->service('http')->fetch(d()->site_path . '/themes/');
d()->service('http')->fetch(d()->site_path . '/libraries/');
// Questionable... who is authoritive?
d()->service('http')->fetch(d()->site_path . '/local.settings.php');
}
/**
* Generate a settings file for the site.
*
* @param url
* The url of the site being invoked.
* @param data
* A reference to the associated array containing the data for the site. This needs to be a reference,
* because the modules might provide additional information about the site.
*/
function _provision_drupal_create_settings_file() {
$config = new Provision_Config_Drupal_Settings(d()->name, drush_get_context('site'));
$config->write();
}
/**
* Create the directories needed to host a drupal site
*
* Also maintains permissions on existing directories.
*/
function _provision_drupal_create_directories($url = NULL, $profile = NULL) {
if (is_null($url)) {
if (d()->type == 'site') {
$url = d()->uri;
}
else {
$url = 'all';
}
}
# those directories will be created and their modes changed
$mkdir = array(
"sites/$url" => 0755,
# those should be writable by the aegir primary group to ease development
"sites/$url/themes" => 02775,
"sites/$url/modules" => 02775,
"sites/$url/libraries" => 02775, # http://drupal.org/node/496240
);
$chgrp = array();
// special case: platform. do not handle files dir
if ($url != 'all') {
$mkdir["sites/$url/files"] = 02770;
$chgrp["sites/$url/files"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/tmp"] = 02770;
$chgrp["sites/$url/files/tmp"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/images"] = 02770;
$chgrp["sites/$url/files/images"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/pictures"] = 02770;
$chgrp["sites/$url/files/pictures"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/css"] = 02770;
$chgrp["sites/$url/files/css"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/js"] = 02770;
$chgrp["sites/$url/files/js"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/ctools"] = 02770;
$chgrp["sites/$url/files/ctools"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/imagecache"] = 02770;
$chgrp["sites/$url/files/imagecache"] = d('@server_master')->web_group;
$mkdir["sites/$url/files/locations"] = 02770;
$chgrp["sites/$url/files/locations"] = d('@server_master')->web_group;
// d7 support
$mkdir["sites/$url/private"] = 02770;
$chgrp["sites/$url/private"] = d('@server_master')->web_group;
$mkdir["sites/$url/private/files"] = 02770;
$chgrp["sites/$url/private/files"] = d('@server_master')->web_group;
$mkdir["sites/$url/private/temp"] = 02770;
$chgrp["sites/$url/private/temp"] = d('@server_master')->web_group;
}
// These paths should not have recursive operations performed on them.
$not_recursive = array(
"sites/$url",
"sites/$url/files",
"sites/$url/files/tmp",
"sites/$url/files/images",
"sites/$url/files/pictures",
"sites/$url/files/css",
"sites/$url/files/js",
"sites/$url/files/ctools",
"sites/$url/files/imagecache",
"sites/$url/files/locations",
"sites/$url/private",
"sites/$url/private/files",
"sites/$url/private/temp"
);
foreach ($mkdir as $path => $perm) {
if (!is_dir($path)) {
provision_file()->mkdir($path)
->succeed('Created @path
')
->fail('Could not create @path
', 'DRUSH_PERM_ERROR');
}
provision_file()->chmod($path, $perm, !in_array($path, $not_recursive))
->succeed('Changed permissions of @path
to @perm')
->fail('Could not change permissions @path
to @perm');
}
foreach ($chgrp as $path => $group) {
provision_file()->chgrp($path, $group, !in_array($path, $not_recursive))
->succeed('Changed group ownership of @path
to @gid')
->fail('Could not change group ownership @path
to @gid');
}
}
/**
* Runs an external script to reload all the various drupal caches
*/
function _provision_drupal_rebuild_caches() {
if (d()->type === 'site') {
drush_include_engine('drupal', 'clear');
}
}
/**
* Find available profiles on this platform.
*/
function _provision_find_profiles() {
include_once('includes/install.inc');
if (!$dir = opendir("./profiles")) {
drush_log(dt("Cannot find profiles directory"), 'error');
return FALSE;
}
while (FALSE !== ($name = readdir($dir))) {
$languages = array();
$file = "./profiles/$name/$name.profile";
if ($name == '..' || $name == '.' || !file_exists($file)) {
continue;
}
$profile = new stdClass();
$profile->name = $name;
$profile->filename = $file;
_provision_cvs_deploy($profile);
$profile->info = array();
$info_file = "./profiles/$name/$name.info";
if (file_exists($info_file)) {
$profile->info = provision_parse_info_file($info_file);
}
require_once($profile->filename);
$func = $profile->name . "_profile_details";
if (function_exists($func)) {
$profile->info = array_merge($profile->info, $func());
}
$languages['en'] = 1;
// Find languages available
$files = array_keys(drush_scan_directory('./profiles/' . $name . '/translations', '/\.po$/', array('.', '..', 'CVS'), 0, FALSE, 'filepath'));
$files = array_merge($files, array_keys(drush_scan_directory('./profiles/' . $name , '/\.po$/', array('.', '..', 'CVS'), 0, FALSE, 'filepath')));
if (is_array($files)) {
foreach ($files as $file) {
if (preg_match('!(/|\.)([^\./]+)\.po$!', $file, $langcode)) {
$languages[$langcode[2]] = 1; // use the language name as an index to weed out duplicates
}
}
}
$profile->info['languages'] = array_keys($languages);
// Drupal 7 renamed the default install profile to 'standard'
// Aegir now allows projects to specify an "old short name" to provide an upgrade path when projects get renamed.
if ($profile->name == 'standard') {
$profile->info['old_short_name'] = 'default';
}
$return[$name] = $profile;
drush_log(dt('Found install profile %name', array('%name' => $name)));
}
return $return;
}
function provision_drupal_find_sites() {
$sites = array();
if ($dir = opendir("./sites")) {
while (FALSE !== ($subdir = readdir($dir))) {
// skip internal directory pointers
if ($subdir != '.' && $subdir != '..') {
$file = "./sites/$subdir/settings.php";
if (file_exists("$file") && ($subdir != 'default') && !is_link("./sites/$subdir")) {
$sites[$subdir] = $file;
}
}
}
closedir($dir);
}
else {
drush_log(dt("Cannot find sites directory"), 'error');
$sites = FALSE;
}
return $sites;
}
function _provision_drupal_get_cvs_versions($files) {
foreach ($files as $modulename => $file) {
$project = array();
$project['filename'] = $file->filename;
$project['name'] = $file->name;
$file->info['description'] = str_replace("\n", "", $file->info['description']);
if (!isset($project['project'])) {
$project['project'] = cvs_deploy_get_project_name($project);
}
_cvs_deploy_version_alter($file->info['version'], $project);
$name = !empty($project['project']) ? $project['project'] : $modulename;
$files[$name] = $file;
}
return $files;
}
/**
* Retrieve a list of aliases for the curent site.
*/
function provision_drupal_find_aliases() {
$aliases = array();
if (d()->type === 'site') {
if (drush_drupal_major_version() >= 7) {
$config = new Provision_Config_Drupal_Alias_Store(d()->name);
$aliases = $config->find();
}
else {
if ($dir = opendir(d()->root . "/sites")) {
while (FALSE !== ($subdir = readdir($dir))) {
// skip internal directory pointers
if ($subdir != '.' && $subdir != '..') {
$path = d()->root . '/sites/' . $subdir;
if (is_link($path)) {
if (d()->uri === readlink($path)) {
$aliases[] = $subdir;
}
}
}
}
closedir($dir);
}
}
}
return $aliases;
}
/**
* Create and remove symlinks for each of the possible domain aliases of an
* existing site.
*/
function _provision_drupal_maintain_aliases() {
if (d()->type === 'site') {
if (drush_drupal_major_version() >= 7) {
$config = new Provision_Config_Drupal_Alias_Store(d()->name);
$config->maintain();
$config->write();
d()->service('http')->sync($config->filename());
}
else {
_provision_drupal_delete_aliases();
if (!d()->redirection) {
foreach (d()->aliases as $alias) {
if ($alias = trim($alias)) {
provision_file()->symlink(d()->uri, d()->root . '/sites/' . $alias)
->succeed('Created symlink for alias @target')
->fail('Could not create symlink for alias @target');
d()->service('http')->sync(d()->root . '/sites/' . $alias);
}
}
}
}
}
}
/**
* Delete a list of aliases
*/
function _provision_drupal_delete_aliases() {
if (d()->type === 'site') {
if (drush_drupal_major_version() >= 7) {
$config = new Provision_Config_Drupal_Alias_Store(d()->name);
$config->delete();
$config->write();
d()->service('http')->sync($config->filename());
}
else {
$aliases = provision_drupal_find_aliases();
foreach ($aliases as $alias) {
$path = d()->root . '/sites/' . $alias;
provision_file()->unlink($path)
->succeed('Removed symlink for alias @path')
->fail('Could not remove symlink for alias @path');
d()->service('http')->sync($path);
}
}
}
}
require_once(dirname(__FILE__) . '/cvs_deploy.inc');
function provision_find_packages() {
// Load the version specific include files.
drush_include_engine('drupal', 'packages', drush_drupal_major_version());
$packages['base'] = _provision_find_packages('base');
$packages['sites-all'] = _provision_find_packages('sites', 'all');
// Create a package for the Drupal release
$packages['base']['platforms'] = _provision_find_platforms();
// Find install profiles.
$profiles = _provision_find_profiles();
drush_set_option('profiles', array_keys((array) $profiles), 'drupal');
// Iterate through the install profiles, finding the profile specific packages
foreach ($profiles as $profile => $info) {
_provision_cvs_deploy($info);
if (!$info->version) {
$info->version = drush_drupal_version();
}
$packages['base']['profiles'][$profile] = $info;
$packages['profiles'][$profile] = _provision_find_packages('profiles', $profile);
}
return $packages;
}
function _provision_find_platforms() {
return array(
'drupal' => array(
'short_name' => 'drupal', 'version' => drush_drupal_version(),
'description' => dt("This platform is running @short_name @version", array('@short_name' => 'Drupal', '@version' => VERSION))));
}
/**
* A small helper function to reduce code duplication
*/
function _provision_find_packages($scope, $key = '') {
$packages = array();
$scope_text = ($key) ? "$scope/$key" : $scope;
foreach (array('modules', 'themes') as $type) {
$packages[$type] = array();
$func = "_provision_drupal_find_$type";
$result = $func($scope, $key);
if (sizeof($result)) {
$packages[$type] = $result;
drush_log(dt("Found !count !type in !scope",
array('!count' => sizeof($result),
'!scope' => $scope_text, '!type' => $type)));
}
}
return $packages;
}
/**
* Map the system table to a packages multi-dimensional array component
*/
function provision_drupal_system_map() {
// Load the version specific include files.
drush_include_engine('drupal', 'packages');
$profiles = _provision_find_profiles();
foreach ($profiles as $profile => $info) {
_provision_cvs_deploy($info);
if (!$info->version) {
$info->version = drush_drupal_version();
}
$profiles[$profile] = $info;
}
$packages['platforms'] = _provision_find_platforms();
$profile = drush_get_option('profile');
$packages['profiles'][$profile] = $profiles[$profile];
$packages['profiles'][$profile]->status = 1;
foreach (_provision_system_query("module") as $module) {
$frags = explode("/", $module->filename);
// ignore site-specific modules
if ($frags[0] == 'sites' && $frags[1] != 'all') {
continue;
}
$info_file = sprintf("%s/%s.info", dirname($module->filename), $module->name);
$module->info = provision_parse_info_file($info_file);
_provision_cvs_deploy($module);
$module->filename = realpath($module->filename);
if ($module->schema_version == -1) {
$module->schema_version = 0;
}
$packages['modules'][$module->name] = $module;
}
drush_log(dt("Found !count modules", array('!count' => sizeof($packages['modules']))));
// XXX: mostly a copy-paste from above
foreach (_provision_system_query("theme") as $theme) {
$frags = explode("/", $theme->filename);
// ignore site-specific themes
if ($frags[0] == 'sites' && $frags[1] != 'all') {
continue;
}
$info_file = sprintf("%s/%s.info", dirname($theme->filename), $theme->name);
$theme->info = provision_parse_info_file($info_file);
_provision_cvs_deploy($theme);
$theme->filename = realpath($theme->filename);
if ($theme->schema_version == -1) {
$theme->schema_version = 0;
}
$packages['themes'][$theme->name] = $theme;
}
drush_log(dt("Found !count themes", array('!count' => sizeof($packages['themes']))));
return $packages;
}
/**
* Retrieve a list of paths to search in a certain scope
*/
function _provision_drupal_search_paths($scope, $key = '', $type = 'modules') {
$searchpaths = array();
$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
switch ($scope) {
case 'base' :
$searchpaths[] = sprintf("%s/%s", $drupal_root, $type);
break;
default :
if ($key) {
$searchpaths[] = sprintf("%s/%s/%s/%s", $drupal_root, $scope, $key, $type);
}
break;
}
return $searchpaths;
}
/**
* Find modules in a certain scope.
*
* This function is general enough that it works for all supported
* versions of Drupal.
*/
function _provision_drupal_find_modules($scope, $key = '') {
$paths = _provision_drupal_search_paths($scope, $key, 'modules');
$files = array();
foreach ($paths as $path) {
$files = array_merge($files, drush_scan_directory($path, "/\.module$/", array('.', '..', 'CVS', '.svn'), 0, TRUE, 'name'));
}
foreach ($files as $name => $info) {
$install_file = sprintf("%s/%s.install", dirname($info->filename), $name);
$schema_version = 0;
if (file_exists($install_file)) {
$source = file_get_contents(trim($install_file));
$source = str_replace("\r\n", "\n", $source);
$source = str_replace("\r", "\n", $source);
$function_matches = array();
preg_match_all('!function\s*&?([a-zA-Z0-9_]+)_update_([0-9]+)\s*\(.*?\s*\{!', $source, $function_matches);
if (sizeof($function_matches[0])) {
$schema_version = max($function_matches[2]);
}
}
$info_file = sprintf("%s/%s.info", dirname($info->filename), $name);
$files[$name]->info = provision_parse_info_file($info_file);
$files[$name]->schema_version = $schema_version;
_provision_cvs_deploy($files[$name]);
}
return $files;
}
function provision_parse_info_file($filename) {
$info = array();
$defaults = array(
'dependencies' => array(),
'description' => '',
'version' => NULL,
'php' => DRUPAL_MINIMUM_PHP,
);
if (file_exists($filename)) {
$info = _provision_drupal_parse_info_file($filename);
}
// Merge in defaults and return
return $info + $defaults;
}
/**
* Set up the $_SERVER environment variable so that drupal can correctly parse the settings.php file.
* The real credentials are stored in the Apache vhost of the relevant site, to prevent leaking of
* sensitive data to site administrators with PHP access who might otherwise access such credentials
* potentially of other sites' settings.php in a multisite set-up.
*/
function provision_prepare_environment() {
$fields = array('db_type', 'db_host', 'db_user', 'db_passwd', 'db_name', 'db_port');
foreach ($fields as $key) {
$_SERVER[$key] = drush_get_option($key, NULL, 'site');
}
// As of Drupal 7 there is no more mysqli type
if (drush_drupal_major_version() >= 7) {
$_SERVER['db_type'] = ($_SERVER['db_type'] == 'mysqli') ? 'mysql' : $_SERVER['db_type'];
}
}
/**
* Reload drushrc files (if available) from several possible locations.
*
* Because the base drush_load_config method only uses an include_once,
* we run into issues when provision commands call other commands that
* modify these config files.
*
* For the changes to become available, and more importantly passed to the
* front end, we need to call this function after calling provision commands.
*/
function provision_reload_config($context, $file = NULL) {
$file = ($file) ? $file : _drush_config_file($context);
if (file_exists($file)) {
drush_log("Reloading $context drushrc.php from $file");
include($file);
// $options will be defined by the config file included above.
if (sizeof($options)) {
$options = array_merge(drush_get_context($context, array()), $options);
drush_set_context($context, $options);
}
}
}
/**
* Maintain a symlink to the site within a client directory
*
* This creates a directory structure like this:
*
* ~/clients/foo/example.org -> ~/platforms/.../sites/example.org
* ~/clients/bar/bar.example.com -> ~/platforms/.../sites/bar.example.com
*
* @todo this probably doesn't belong in this file
*/
function _provision_client_create_symlink() {
if (d()->client_name) {
$sites_dir = d()->server->clients_path . '/' . d()->client_name;
provision_file()->create_dir($sites_dir, dt('Client home directory for @client', array('@client' => d()->client_name)), 0750);
_provision_client_delete_old_symlink();
provision_file()->symlink(d()->site_path, $sites_dir . '/' . d()->uri)
->succeed('Created symlink @path to @target')
->fail('Could not create symlink @path to @target: @reason');
}
}
/**
* Delete dangling symlinks for this site.
*
* This is a crude implementation, as we do not have the old client name so we
* need to iterate over the directories. We only remove the first entry we
* find to save some I/O.
*/
function _provision_client_delete_old_symlink() {
$previous = d()->server->clients_path . '/' . d()->client_name . '/' . d()->uri;
// this is necessary because unlink doesn't fail on missing files (!)
$found = (file_exists($previous) || is_link($previous));
provision_file()->unlink($previous);
if (!$found) {
drush_log(dt("couldn't find previous client symlink, iterating through all sites"));
// only iterate if the symlink location changed
if ($dh = @opendir(d()->server->clients_path)) {
while (($file = readdir($dh)) !== false) {
if ($file != '.' && $file != '..') {
$path = d()->server->clients_path . '/' . $file . '/' . d()->uri;
if (file_exists($path) || is_link($path)) {
provision_file()->unlink($path);
drush_log(dt("removed previous symlink in @path", array("@path" => $path)), 'success');
break; // found it
}
}
}
closedir($dh);
}
}
}
/**
* Delete the site symlink within the client directory
*
* This deletes the site symlink created on verify/install
*
* @see _provision_client_create_symlink()
*/
function _provision_client_delete_symlink() {
if (d()->client_name) {
provision_file()->unlink(d()->server->clients_path . '/' . d()->client_name . '/' . d()->uri)
->succeed('Deleted symlink @path')
->fail('Failed to delete symlink @path: @reason');
}
}