--- /dev/null
+<?php
+
+/**
+ * @file
+ * The site alias API.
+ *
+ * Run commands on remote server(s).
+ * @see example.aliases.drushrc.php
+ * @see http://drupal.org/node/670460
+ */
+
+use Drush\Log\LogLevel;
+use Webmozart\PathUtil\Path;
+
+/**
+ * Check to see if the user specified an alias
+ * in an arguement, or via site-set. If so, return
+ * the name of the alias.
+ *
+ * If the alias came from args, then remove it
+ * from args.
+ */
+function drush_sitealias_check_arg_and_site_set() {
+ $args = drush_get_arguments();
+ $target_alias = FALSE;
+
+ // Test to see if the first arg is a valid alias identifier.
+ // If the first arguement is a well-formed identifier, but we
+ // cannot find a record for it, then we will fail with an error.
+ if (!empty($args) && drush_sitealias_valid_alias_format($args[0])) {
+ // Pop the alias off the arguments list first thing.
+ $target_alias = array_shift($args);
+ drush_set_arguments($args);
+ }
+ else {
+ // If the user did not specify an alias via an argument,
+ // check to see if a site env was set.
+ $target_alias = drush_sitealias_site_get();
+ }
+
+ // Record the user's desired target alias name
+ if ($target_alias) {
+ drush_set_context('DRUSH_TARGET_SITE_ALIAS', $target_alias);
+ }
+ return $target_alias;
+}
+
+/**
+ * Check to see if the first command-line arg or the
+ * -l option is a site alias; if it is, copy its record
+ * values to the 'alias' context.
+ *
+ * @return boolean
+ * TRUE if a site alias was found and processed.
+ */
+function drush_sitealias_check_arg() {
+ $args = drush_get_arguments();
+
+ // Test to see if the first arg is a site specification
+ if (!empty($args) && _drush_sitealias_set_context_by_name($args[0])) {
+ drush_set_context('DRUSH_TARGET_SITE_ALIAS', $args[0]);
+ array_shift($args);
+ // We only need to expand the site specification
+ // once, then we are done.
+ drush_set_arguments($args);
+ return TRUE;
+ }
+ // Return false to indicate that no site alias was specified.
+ return FALSE;
+}
+
+/*
+ * Check to see if user has selected a site via site-set command.
+ */
+function drush_sitealias_check_site_env() {
+ $site = drush_get_context('DRUSH_TARGET_SITE_ALIAS');
+ if (empty($site)) {
+ $site_env = drush_sitealias_site_get();
+ if (!empty($site_env) && (_drush_sitealias_set_context_by_name($site_env))) {
+ drush_set_context('DRUSH_TARGET_SITE_ALIAS', $site_env);
+ return TRUE;
+ }
+ }
+ // Return false to indicate that no site alias was specified.
+ return FALSE;
+}
+
+/**
+ * Check to see if a '@self' record was created during bootstrap.
+ * If not, make one now.
+ */
+function drush_sitealias_create_self_alias() {
+ $self_record = drush_sitealias_get_record('@self');
+ if (!array_key_exists('root', $self_record) && !array_key_exists('remote-host', $self_record)) {
+ $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
+ $uri = drush_get_context('DRUSH_SELECTED_URI');
+ if (!empty($drupal_root) && !empty($uri)) {
+ // Create an alias '@self'
+ _drush_sitealias_cache_alias('@self', array('root' => $drupal_root, 'uri' => $uri));
+ }
+ }
+}
+
+/**
+ * Given a list of alias records, shorten the name used if possible
+ */
+function drush_sitealias_simplify_names($site_list) {
+ $result = array();
+ foreach ($site_list as $original_name => $alias_record) {
+ $adjusted_name = $alias_record['#name'];
+ $hashpos = strpos($original_name, '#');
+ if ($hashpos !== FALSE) {
+ $adjusted_name = substr($original_name, $hashpos);
+ if (array_key_exists('remote-host', $alias_record)) {
+ $adjusted_name = $alias_record['remote-host'] . $adjusted_name;
+ }
+ }
+ $result[$adjusted_name] = $alias_record;
+ }
+ return $result;
+}
+
+/**
+ * Given an array of site specifications, resolve each one in turn and
+ * return an array of alias records. If you only want a single record,
+ * it is preferable to simply call drush_sitealias_get_record() directly.
+ *
+ * @param $site_specifications
+ * One of:
+ * A comma-separated list of site specifications: '@site1,@site2'
+ * An array of site specifications: array('@site1','@site2')
+ * An array of alias records:
+ * array(
+ * 'site1' => array('root' => ...),
+ * 'site2' => array('root' => ...)
+ * )
+ * An array of site specifications.
+ * @see drush_sitealias_get_record() for the format of site specifications.
+ * @return
+ * An array of alias records
+ */
+function drush_sitealias_resolve_sitespecs($site_specifications, $alias_path_context = NULL) {
+ $result_list = array();
+ $not_found = array();
+ if (!is_array($site_specifications)) {
+ $site_specifications = explode(',', $site_specifications);
+ }
+ if (!empty($site_specifications)) {
+ foreach ($site_specifications as $site) {
+ if (is_array($site)) {
+ $result_list[] = $site;
+ }
+ else {
+ $alias_record = drush_sitealias_get_record($site, $alias_path_context);
+ if (!$alias_record) {
+ $not_found[] = $site;
+ }
+ else {
+ $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record));
+ }
+ }
+ }
+ }
+ return array($result_list, $not_found);
+}
+
+/**
+ * Returns TRUE if $alias is a valid format for an alias name.
+ *
+ * Mirrors the allowed formats shown below for drush_sitealias_get_record.
+ */
+function drush_sitealias_valid_alias_format($alias) {
+ return ( (strpos($alias, ',') !== false) ||
+ ((strpos($alias, '@') === FALSE ? 0 : 1) + (strpos($alias, '/') === FALSE ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) ||
+ ($alias{0} == '#') ||
+ ($alias{0} == '@')
+ );
+}
+
+/**
+ * Get a site alias record given an alias name or site specification.
+ *
+ * If it is the name of a site alias, return the alias record from
+ * the site aliases array.
+ *
+ * If it is the name of a folder in the 'sites' folder, construct
+ * an alias record from values stored in settings.php.
+ *
+ * If it is a site specification, construct an alias record from the
+ * values in the specification.
+ *
+ * Site specifications come in several forms:
+ * - /path/to/drupal#sitename
+ * - user@server/path/to/drupal#sitename
+ * - user@server/path/to/drupal (sitename == server)
+ * - user@server#sitename (only if $option['r'] set in some drushrc file on server)
+ * - #sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
+ * - sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
+ *
+ * Note that in the case of the first four forms, it is also possible
+ * to add additional site variable to the specification using uri query
+ * syntax. For example:
+ *
+ * user@server/path/to/drupal?db-url=...#sitename
+ *
+ * @param alias
+ * An alias name or site specification
+ * @return array
+ * An alias record, or empty if none found.
+ */
+function drush_sitealias_get_record($alias, $alias_context = NULL) {
+ // Check to see if the alias contains commas. If it does, then
+ // we will go ahead and make a site list record
+ $alias_record = array();
+ if (strpos($alias, ',') !== false) {
+ // TODO: If the site list contains any site lists, or site
+ // search paths, then we should expand those and merge them
+ // into this list longhand.
+ $alias_record['site-list'] = explode(',', $alias);
+ }
+ else {
+ $alias_record = _drush_sitealias_get_record($alias, $alias_context);
+ }
+ if (!empty($alias_record)) {
+ if (array_key_exists('#name', $alias_record)) {
+ if ($alias_record['#name'] == 'self') {
+ $path = drush_sitealias_local_site_path($alias_record);
+ if ($path) {
+ $cached_alias_record = drush_sitealias_lookup_alias_by_path($path);
+ // Don't overrite keys which have already been negotiated.
+ unset($cached_alias_record['#name'], $cached_alias_record['root'], $cached_alias_record['uri']);
+ $alias_record = array_merge($alias_record, $cached_alias_record);
+ }
+ }
+ }
+ else {
+ $alias_record['#name'] = drush_sitealias_uri_to_site_dir($alias);
+ }
+ }
+ return $alias_record;
+}
+
+/**
+ * This is a continuation of drush_sitealias_get_record, above. It is
+ * not intended to be called directly.
+ */
+function _drush_sitealias_get_record($alias, $alias_context = NULL) {
+ $alias_record = array();
+ // Before we do anything else, load $alias if it needs to be loaded
+ _drush_sitealias_load_alias($alias, $alias_context);
+
+ // Check to see if the provided parameter is in fact a defined alias.
+ $all_site_aliases =& drush_get_context('site-aliases');
+ if (array_key_exists($alias, $all_site_aliases)) {
+ $alias_record = $all_site_aliases[$alias];
+ }
+ // If the parameter is not an alias, then it is some form of
+ // site specification (or it is nothing at all)
+ else {
+ if (isset($alias)) {
+ // Cases 1.) - 4.):
+ // We will check for a site specification if the alias has at least
+ // two characters from the set '@', '/', '#'.
+ if ((strpos($alias, '@') === FALSE ? 0 : 1) + ((strpos($alias, '/') === FALSE && strpos($alias, '\\') === FALSE) ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) {
+ if ((substr($alias,0,7) != 'http://') && !drush_is_absolute_path($alias)) {
+ // Add on a scheme so that "user:pass@server" will always parse correctly
+ $parsed = parse_url('http://' . $alias);
+ }
+ else if (drush_is_windows() && drush_is_absolute_path($alias)) {
+ // On windows if alias begins with a filesystem path we must add file:// scheme to make it parse correcly
+ $parsed = parse_url('file:///' . $alias);
+ }
+ else {
+ $parsed = parse_url($alias);
+ }
+ // Copy various parts of the parsed URL into the appropriate records of the alias record
+ foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => 'root') as $url_key => $option_key) {
+ if (array_key_exists($url_key, $parsed)) {
+ _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]);
+ }
+ }
+ // If the site specification has a query, also set the query items
+ // in the alias record. This allows passing db_url as part of the
+ // site specification, for example.
+ if (array_key_exists('query', $parsed)) {
+ foreach (explode('&', $parsed['query']) as $query_arg) {
+ $query_components = explode('=', $query_arg);
+ _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1]));
+ }
+ }
+
+ // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host
+ // Note: We presume that 'server' is the best default for case 3; without this code, the default would
+ // be whatever is set in $options['l'] on the target machine's drushrc.php settings file.
+ if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) {
+ $alias_record['uri'] = $parsed['host'];
+ }
+
+ // Special checking: relative aliases embedded in a path
+ $relative_alias_pos = strpos($alias_record['root'], '/@');
+ if ($relative_alias_pos !== FALSE) {
+ // Special checking: /path/@sites
+ $base = substr($alias_record['root'], 0, $relative_alias_pos);
+ $relative_alias = substr($alias_record['root'], $relative_alias_pos + 1);
+ if (drush_valid_root($base) || ($relative_alias == '@sites')) {
+ drush_sitealias_create_sites_alias($base);
+ $alias_record = drush_sitealias_get_record($relative_alias);
+ }
+ else {
+ $alias_record = array();
+ }
+ }
+ }
+ else {
+ // Case 5.) and 6.):
+ // If the alias is the name of a folder in the 'sites' directory,
+ // then use it as a local site specification.
+ $alias_record = _drush_sitealias_find_record_for_local_site($alias);
+ }
+ }
+ }
+
+ if (!empty($alias_record)) {
+ if (!isset($alias_record['remote']) && !isset($alias_record['#loaded-config'])) {
+ if (array_key_exists('root', $alias_record)) {
+ drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush');
+ drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush');
+ }
+ // TODO: We should probably remove this feature, and put it back
+ // in, but in different places (e.g. site selection, sql-sync + rsync
+ // parameters, etc.)
+ $alias_site_dir = drush_sitealias_local_site_path($alias_record);
+
+ if (isset($alias_site_dir)) {
+ // Add the sites folder of this site to the alias search path list
+ drush_sitealias_add_to_alias_path($alias_site_dir);
+ }
+ if (isset($alias_record['config']) && file_exists($alias_record['config'])) {
+ drush_load_config_file('site', $alias_record['config']);
+ $alias_record['#loaded-config'] = TRUE;
+ }
+ unset($alias_record['config']);
+ }
+
+ // Add the static defaults
+ _drush_sitealias_add_static_defaults($alias_record);
+
+ // Cache the result with all of its calculated values
+ $all_site_aliases[$alias] = $alias_record;
+ }
+
+ return $alias_record;
+}
+
+/**
+ * Add a path to the array of paths where alias files are searched for.
+ *
+ * @param $add_path
+ * A path to add to the search path (or NULL to not add any).
+ * Once added, the new path will remain available until drush
+ * exits.
+ * @return
+ * An array of paths containing all values added so far
+ */
+function drush_sitealias_add_to_alias_path($add_path) {
+ static $site_paths = array();
+
+ if ($add_path != NULL) {
+ if (!is_array($add_path)) {
+ $add_path = explode(PATH_SEPARATOR, $add_path);
+ }
+ // Normalize path to make sure we don't add the same path twice on
+ // windows due to different spelling. e.g. c:\tmp and c:/tmp
+ foreach($add_path as &$path) {
+ $path = drush_normalize_path($path);
+ }
+ $site_paths = array_unique(array_merge($site_paths, $add_path));
+ }
+ return $site_paths;
+}
+
+/**
+ * Return the array of paths where alias files are searched for.
+ *
+ * @param $alias_path_context
+ * If the alias being looked up is part of a relative alias,
+ * the alias path context specifies the context of the primary
+ * alias the new alias is rooted from. Alias files stored in
+ * the sites folder of this context, or inside the context itself
+ * takes priority over any other search path that might define
+ * a similarly-named alias. In this way, multiple sites can define
+ * a '@peer' alias.
+ * @return
+ * An array of paths
+ */
+function drush_sitealias_alias_path($alias_path_context = NULL) {
+ $context_path = array();
+ if (isset($alias_path_context)) {
+ $context_path = array(drush_sitealias_local_site_path($alias_path_context));
+ }
+ // We get the current list of site paths by adding NULL
+ // (nothing) to the path list, which is a no-op
+ $site_paths = drush_sitealias_add_to_alias_path(NULL);
+
+ // If the user defined the root of a drupal site, then also
+ // look for alias files in /drush and /sites/all/drush.
+ $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
+ if (!empty($drupal_root)) {
+ $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/../drush');
+ $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/drush');
+ $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/sites/all/drush');
+ $uri = drush_get_context('DRUSH_SELECTED_URI');
+ if (empty($uri)) {
+ $uri = 'default';
+ }
+ $site_dir = drush_sitealias_uri_to_site_dir($uri, $drupal_root);
+ if ($site_dir) {
+ $site_paths[] = drush_sitealias_alias_base_directory("$drupal_root/sites/$site_dir");
+ }
+ }
+ $alias_path = (array) drush_get_context('ALIAS_PATH', array());
+ return array_unique(array_merge($context_path, $alias_path, $site_paths));
+}
+
+/**
+ * If there is a directory 'site-aliases' in the specified search location,
+ * then search ONLY in that directory for aliases. Otherwise, search
+ * anywhere inside the specified directory for aliases.
+ */
+function drush_sitealias_alias_base_directory($dir) {
+ $potential_location = $dir . '/site-aliases';
+ if (is_dir($potential_location)) {
+ return $potential_location;
+ }
+ return $dir;
+}
+
+/**
+ * Return the full path to the site directory of the
+ * given alias record.
+ *
+ * @param $alias_record
+ * The alias record
+ * @return
+ * The path to the site directory of the associated
+ * alias record, or NULL if the record is not a local site.
+ */
+function drush_sitealias_local_site_path($alias_record) {
+ $result = NULL;
+
+ if (isset($alias_record['root']) && !isset($alias_record['remote-host'])) {
+ if (isset($alias_record['uri'])) {
+ $uri = $alias_record['uri'];
+ $uri = preg_replace('#^[^:]*://#', '', $uri);
+ while (!$result && !empty($uri)) {
+ if (file_exists($alias_record['root'] . '/sites/sites.php')) {
+ $sites = array();
+ include($alias_record['root'] . '/sites/sites.php');
+ if (array_key_exists($uri, $sites)) {
+ $result = $alias_record['root'] . '/sites/' . $sites[$uri];
+ }
+ }
+ if (!$result) {
+ $result = ($alias_record['root'] . '/sites/' . drush_sitealias_uri_to_site_dir($uri, drush_sitealias_get_root($alias_record)));
+ }
+ $result = realpath($result);
+ $uri = preg_replace('#^[^.]*\.*#', '', $uri);
+ }
+ }
+ if (!$result) {
+ $result = realpath($alias_record['root'] . '/sites/default');
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Check and see if an alias definition for $alias is available.
+ * If it is, load it into the list of aliases cached in the
+ * 'site-aliases' context.
+ *
+ * @param $alias
+ * The name of the alias to load in ordinary form ('@name')
+ * @param $alias_path_context
+ * When looking up a relative alias, the alias path context is
+ * the primary alias that we will start our search from.
+ */
+function _drush_sitealias_load_alias($alias, $alias_path_context = NULL) {
+ $all_site_aliases = drush_get_context('site-aliases');
+ $result = array();
+
+ // Only aliases--those named entities that begin with '@'--can be loaded this way.
+ // We also skip any alias that has already been loaded.
+ if ((substr($alias,0,1) == '@') && !array_key_exists($alias,$all_site_aliases)) {
+ $aliasname = substr($alias,1);
+ $result = _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context);
+ if (!empty($result)) {
+ $alias_options = array('site-aliases' => array($aliasname => $result));
+ _drush_sitealias_add_inherited_values($alias_options['site-aliases']);
+ drush_set_config_special_contexts($alias_options);
+ if (array_key_exists('#file', $result)) {
+ drush_log(dt('Loaded alias !alias from file !file', array('!alias' => $alias, '!file' => $result['#file'])));
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Load every alias file that can be found anywhere in the
+ * alias search path.
+ */
+function drush_sitealias_load_all($resolve_parent = TRUE) {
+ $result = _drush_sitealias_find_and_load_all_aliases();
+ if (!empty($result) && ($resolve_parent == TRUE)) {
+ // If any aliases were returned, then check for
+ // inheritance and then store the aliases into the
+ // alias cache
+ _drush_sitealias_add_inherited_values($result);
+ $alias_options = array('site-aliases' => $result);
+ drush_set_config_special_contexts($alias_options);
+ }
+}
+
+/**
+ * Worker function called by _drush_sitealias_load_alias and
+ * drush_sitealias_load_all. Traverses the alias search path
+ * and finds the specified alias record.
+ *
+ * @return
+ * An array of $kay => $value pair of alias names and alias records
+ * loaded.
+ */
+function _drush_sitealias_find_and_load_all_aliases() {
+ $result = array();
+
+ $drush_alias_files = _drush_sitealias_find_alias_files();
+ drush_set_context('drush-alias-files', $drush_alias_files);
+
+ // For every file that matches, check inside it for
+ // an alias with a matching name.
+ foreach ($drush_alias_files as $filename) {
+ if (file_exists($filename)) {
+ $aliases = $options = array();
+ // silently ignore files we can't include
+ if ((@include $filename) === FALSE) {
+ drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
+ continue;
+ }
+ unset($options['site-aliases']); // maybe unnecessary
+
+ // If $aliases are not set, but $options are, then define one alias named
+ // after the first word of the file, before '.alias.drushrc.php.
+ if (empty($aliases) && !empty($options)) {
+ $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
+ $aliases[$this_alias_name] = $options;
+ $options = array();
+ }
+ // If this is a group alias file, then make an
+ // implicit alias from the group name that contains
+ // a site-list of all of the aliases in the file
+ $group_name = '';
+ if (substr($filename, -20) == ".aliases.drushrc.php") {
+ $group_name = basename($filename,".aliases.drushrc.php");
+ if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
+ $alias_names = array();
+ foreach (array_keys($aliases) as $one_alias) {
+ $alias_names[] = "@$group_name.$one_alias";
+ $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
+ unset($aliases[$one_alias]);
+ }
+ $aliases[$group_name] = array('site-list' => implode(',', $alias_names));
+ }
+ }
+ if (!empty($aliases)) {
+ if (!empty($options)) {
+ foreach ($aliases as $name => $value) {
+ $aliases[$name] = array_merge($options, $value);
+ }
+ $options = array();
+ }
+
+ foreach ($aliases as $name => $value) {
+ _drush_sitealias_initialize_alias_record($aliases[$name]);
+ $aliases[$name]['#name'] = $name;
+ $aliases[$name]['#file'] = $filename;
+ }
+
+ $result = _sitealias_array_merge($result, $aliases);
+ // If we found at least one alias from this file
+ // then record it in the drush-alias-files context.
+ $drush_alias_files = drush_get_context('drush-alias-files');
+ if (!in_array($filename, $drush_alias_files)) {
+ $drush_alias_files[] = $filename;
+ }
+ drush_set_context('drush-alias-files', $drush_alias_files);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Function to find all alias files that might contain aliases
+ * that match the requested alias name.
+ */
+function _drush_sitealias_find_alias_files($aliasname = NULL, $alias_path_context = NULL) {
+ $alias_files_to_consider = array();
+
+ // The alias path is a list of folders to search for alias settings files
+ $alias_path = drush_sitealias_alias_path($alias_path_context);
+
+ // $alias_files contains a list of filename patterns
+ // to search for. We will find any matching file in
+ // any folder in the alias path. The directory scan
+ // is not deep, though; only files immediately in the
+ // search path are considered.
+ $alias_files = array('/.*aliases\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/');
+ if ($aliasname == NULL) {
+ $alias_files[] = '/.*\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/';
+ }
+ else {
+ $alias_files[] = '/' . preg_quote($aliasname, '/') . '\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/';
+ }
+
+ // Do not scan into the files directory.
+ $blacklist = array_merge(array('files'), drush_filename_blacklist());
+
+ // Search each path in turn.
+ foreach ($alias_path as $path) {
+ // Find all of the matching files in this location
+ foreach ($alias_files as $file_pattern_to_search_for) {
+ drush_log(dt('Scanning into @path for @pattern', array('@path' => $path, '@pattern' => $file_pattern_to_search_for)), LogLevel::DEBUG_NOTIFY);
+ $alias_files_to_consider = array_merge($alias_files_to_consider, array_keys(drush_scan_directory($path, $file_pattern_to_search_for, $blacklist, 0, TRUE)));
+ }
+ }
+
+ return $alias_files_to_consider;
+}
+
+/**
+ * Traverses the alias search path and finds the specified alias record.
+ *
+ * @param $aliasname
+ * The name of the alias without the leading '@' (i.e. '#name')
+ * or NULL to load every alias found in every alias file.
+ * @param $alias_path_context
+ * When looking up a relative alias, the alias path context is
+ * the primary alias that we will start our search from.
+ * @return
+ * An empty array if nothing was loaded. If $aliasname is
+ * not null, then the array returned is the alias record for
+ * $aliasname. If $aliasname is NULL, then the array returned
+ * is a $kay => $value pair of alias names and alias records
+ * loaded.
+ */
+function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = NULL) {
+ // Special checking for '@sites' alias
+ if ($aliasname == 'sites') {
+ $drupal_root = NULL;
+ if ($alias_path_context != null) {
+ if (array_key_exists('root', $alias_path_context) && !array_key_exists('remote-host', $alias_path_context)) {
+ $drupal_root = $alias_path_context['root'];
+ }
+ }
+ else {
+ $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
+ }
+ if (isset($drupal_root) && !is_array($drupal_root)) {
+ drush_sitealias_create_sites_alias($drupal_root);
+ }
+ }
+
+ $alias_files_to_consider = _drush_sitealias_find_alias_files($aliasname, $alias_path_context);
+
+ return _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider);
+}
+
+function _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider) {
+ $result = array();
+ $result_names = array();
+
+ // For every file that matches, check inside it for
+ // an alias with a matching name.
+ $recorded_files = array();
+ foreach ($alias_files_to_consider as $filename) {
+ if (file_exists($filename)) {
+ $aliases = $options = array();
+ // silently ignore files we can't include
+ if ((@include $filename) === FALSE) {
+ drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
+ continue;
+ }
+ unset($options['site-aliases']); // maybe unnecessary
+
+ // If $aliases are not set, but $options are, then define one alias named
+ // after the first word of the file, before '.alias.drushrc.php.
+ if (empty($aliases) && !empty($options)) {
+ $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
+ $aliases[$this_alias_name] = $options;
+ $options = array();
+ }
+ // If this is a group alias file, then make an
+ // implicit alias from the group name that contains
+ // a site-list of all of the aliases in the file
+ $group_prefix = '';
+ if (substr($filename, -20) == ".aliases.drushrc.php") {
+ $group_name = basename($filename,".aliases.drushrc.php");
+ $group_prefix = $group_name . '.';
+ if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
+ $alias_names = array();
+ foreach (array_keys($aliases) as $one_alias) {
+ $alias_names[] = "@$group_name.$one_alias";
+ $aliases[$one_alias]['#name'] = "$group_name.$one_alias";
+ $aliases[$one_alias]['#group'] = $group_name;
+ $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
+ $aliases[$one_alias]["#hidden"] = TRUE;
+ }
+ $aliases[$group_name] = array('site-list' => implode(',', $alias_names), '#group' => $group_name, '#name' => $group_name);
+ }
+ }
+ // Store only the named alias into the alias cache
+ if ((isset($aliases)) && !empty($aliasname) && array_key_exists($aliasname, $aliases)) {
+ drush_set_config_special_contexts($options); // maybe unnecessary
+ $one_result = array_merge($options, $aliases[$aliasname]);
+ $one_result['#file'] = $filename;
+ if (!array_key_exists('#name', $one_result)) {
+ $one_result['#name'] = $aliasname;
+ }
+ _drush_sitealias_initialize_alias_record($one_result);
+ // If the alias name is exactly the same as a previous match, then
+ // merge the two records together
+ if (!empty($result) && ($result['#name'] == $one_result['#name'])) {
+ $result = _sitealias_array_merge($result, $one_result);
+ }
+ // Add the name of the found record to the list of results
+ else {
+ $result_names[] = "@" . $one_result['#name'];
+ $result = $one_result;
+ }
+ }
+ }
+ }
+ // If there are multiple matches, then return a list of results.
+ if (count($result_names) > 1) {
+ $result = array('site-list' => $result_names);
+ }
+
+ return $result;
+}
+
+/**
+ * Merges two site aliases.
+ *
+ * array_merge_recursive is too much; we only want to run
+ * array_merge on the common top-level keys of the array.
+ *
+ * @param array $site_alias_a
+ * A site alias array.
+ * @param array $site_alias_b
+ * A site alias array.
+ * @return
+ * A site alias array where the keys from $site_alias_a are overwritten by the
+ * keys from $site_alias_b.
+ */
+function _sitealias_array_merge($site_alias_a, $site_alias_b) {
+ $result = $site_alias_a;
+
+ foreach($site_alias_b as $key => $value) {
+ if (is_array($value) && array_key_exists($key, $result)) {
+ $result[$key] = array_merge($result[$key], $value);
+ }
+ else {
+ $result[$key] = $value;
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Check to see if there is a 'parent' item in the alias; if there is,
+ * then load the parent alias record and overlay the entries in the
+ * current alias record on top of the items from the parent record.
+ *
+ * @param $aliases
+ * An array of alias records that are modified in-place.
+ */
+function _drush_sitealias_add_inherited_values(&$aliases) {
+ foreach ($aliases as $alias_name => $alias_value) {
+ // Prevent circular references from causing an infinite loop
+ _drush_sitealias_cache_alias("@$alias_name", array());
+ _drush_sitealias_add_inherited_values_to_record($alias_value);
+ $aliases[$alias_name] = $alias_value;
+ }
+}
+
+function _drush_sitealias_add_inherited_values_to_record(&$alias_value) {
+ drush_command_invoke_all_ref('drush_sitealias_alter', $alias_value);
+ if (isset($alias_value['parent'])) {
+ drush_log(dt("Using deprecated 'parent' element '!parent' in '!name'.", array('!parent' => $alias_value['parent'], '!name' => $alias_value['#name'])), LogLevel::DEBUG);
+ // Fetch and merge in each parent
+ foreach (explode(',', $alias_value['parent']) as $parent) {
+ $parent_record = drush_sitealias_get_record($parent);
+ unset($parent_record['#name']);
+ unset($parent_record['#file']);
+ unset($parent_record['#hidden']);
+ $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases'));
+ foreach ($array_based_keys as $array_based_key) {
+ if (isset($alias_value[$array_based_key]) && isset($parent_record[$array_based_key])) {
+ $alias_value[$array_based_key] = array_merge($parent_record[$array_based_key], $alias_value[$array_based_key]);
+ }
+ }
+ $alias_value = array_merge($parent_record, $alias_value);
+ }
+ }
+ unset($alias_value['parent']);
+}
+
+/**
+ * Add an empty record for the specified alias name
+ *
+ * @param $alias_name
+ * The name of the alias, including the leading "@"
+ */
+function _drush_sitealias_cache_alias($alias_name, $alias_record) {
+ $cache =& drush_get_context('site-aliases');
+ // If the alias already exists in the cache, then merge
+ // the new alias with the existing alias
+ if (array_key_exists($alias_name, $cache)) {
+ $alias_record = array_merge($cache[$alias_name], $alias_record);
+ }
+ if (!isset($alias_record['#name'])) {
+ $alias_record['#name'] = trim($alias_name, '@');
+ }
+ $cache[$alias_name] = $alias_record;
+
+ // If the alias record points at a local site, make sure
+ // that /drush, /sites/all/drush and the site folder for that site
+ // are added to the alias path, so that other alias files
+ // stored in those locations become searchable.
+ if (!array_key_exists('remote-host', $alias_record) && !empty($alias_record['root'])) {
+ drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush');
+ drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush');
+ $site_dir = drush_sitealias_local_site_path($alias_record);
+ if (isset($site_dir)) {
+ drush_sitealias_add_to_alias_path($site_dir);
+ }
+ }
+}
+
+/**
+ * If the alias record does not contain a 'databases' or 'db-url'
+ * entry, then use backend invoke to look up the settings value
+ * from the remote or local site. The 'db_url' form is preferred;
+ * nothing is done if 'db_url' is not available (e.g. on a D7 site)
+ *
+ * @param $alias_record
+ * The full alias record to populate with database settings
+ */
+function drush_sitealias_add_db_url(&$alias_record) {
+ if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
+ drush_sitealias_add_db_settings($alias_record);
+ }
+ if (!isset($alias_record['db-url']) && isset($alias_record['databases'])) {
+ $alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']);
+ }
+}
+
+/**
+ * Drush still accepts --db-url format database specifications as
+ * cli parameters; it is therefore useful to be able to convert
+ * from a database record back to a db-url sometimes.
+ */
+function drush_sitealias_convert_db_spec_to_db_url($db_spec) {
+ $result = urlencode($db_spec["driver"]) . "://";
+ if (isset($db_spec["username"])) {
+ $result .= urlencode($db_spec["username"]);
+ if (isset($db_spec["password"])) {
+ $result .= ":" . urlencode($db_spec["password"]);
+ }
+ $result .= "@";
+ }
+ // Host is required, unless this is an sqlite db.
+ if (isset($db_spec["host"])) {
+ $result .= urlencode($db_spec["host"]);
+ if (isset($db_spec["port"])) {
+ $result .= ":" . urlencode($db_spec["port"]);
+ }
+ $result .= '/' . urlencode($db_spec["database"]);
+ }
+ else {
+ // URL-encode the database, but convert slashes
+ // back to their original form for readability.
+ // This portion is the "path" of the URL, so it may
+ // contain slashes. This is important for sqlite.
+ $result .= str_replace("%2F", "/", urlencode(ltrim($db_spec["database"], '/')));
+ }
+ return $result;
+}
+
+/**
+ * Create a db-url from the databases record.
+ */
+function drush_sitealias_convert_databases_to_db_url($databases) {
+ if ((count($databases) == 1) && isset($databases['default'])) {
+ $result = drush_sitealias_convert_db_spec_to_db_url($databases['default']['default']);
+ }
+ else {
+ foreach ($databases as $key => $db_info) {
+ $result[$key] = drush_sitealias_convert_db_spec_to_db_url($db_info['default']);
+ }
+ }
+ return $result;
+}
+
+/**
+ * Return the databases record from the alias record
+ *
+ * @param $alias_record
+ * A record returned from drush_sitealias_get_record
+ * @returns
+ * A databases record (always in D7 format) or NULL
+ * if the databases record could not be found.
+ */
+function sitealias_get_databases_from_record(&$alias_record) {
+ $altered_record = drush_sitealias_add_db_settings($alias_record);
+
+ return array_key_exists('databases', $alias_record) ? $alias_record['databases'] : NULL;
+}
+
+/**
+ * Return the $db_spec record for the database associated with
+ * the provided alias record. @see drush_sitealias_add_db_settings(),
+ * which will be used to first add the database information to the
+ * alias records, invoking sql-conf to look them up if necessary.
+ *
+ * The options 'database' and 'target' are used to specify which
+ * specific database should be fetched from the database record;
+ * they may appear in the alias definition, or may be taken from the
+ * command line options. The values 'default' and 'default' are
+ * used if these options are not specified in either location.
+ *
+ * Note that in the context of sql-sync, the site alias record will
+ * be taken from one of the source or target aliases
+ * (e.g. `drush sql-sync @source @target`), which will be overlayed with
+ * any options that begin with 'source-' or 'target-', respectively.
+ * Therefore, the commandline options 'source-database' and 'source-target'
+ * (or 'target-database' and 'source-target') may also affect the operation
+ * of this function.
+ */
+function drush_sitealias_get_db_spec(&$alias_record, $default_to_self = FALSE, $prefix = '') {
+ $db_spec = NULL;
+ $databases = sitealias_get_databases_from_record($alias_record);
+ if (isset($databases) && !empty($databases)) {
+ $database = drush_sitealias_get_option($alias_record, 'database', 'default', $prefix);
+ $target = drush_sitealias_get_option($alias_record, 'target', 'default', $prefix);
+ if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) {
+ $db_spec = $databases[$database][$target];
+ }
+ }
+ elseif ($default_to_self) {
+ $db_spec = _drush_sql_get_db_spec();
+ }
+
+ if (isset($db_spec)) {
+ $remote_host = drush_sitealias_get_option($alias_record, 'remote-host', NULL, $prefix);
+ if (!drush_is_local_host($remote_host)) {
+ $db_spec['remote-host'] = $remote_host;
+ $db_spec['port'] = drush_sitealias_get_option($alias_record, 'remote-port', (isset($db_spec['port']) ? $db_spec['port'] : NULL), $prefix);
+ }
+ }
+
+ return $db_spec;
+}
+
+/**
+ * If the alias record does not contain a 'databases' or 'db-url'
+ * entry, then use backend invoke to look up the settings value
+ * from the remote or local site. The 'databases' form is
+ * preferred; 'db_url' will be converted to 'databases' if necessary.
+ *
+ * @param $alias_record
+ * The full alias record to populate with database settings
+ */
+function drush_sitealias_add_db_settings(&$alias_record) {
+ $altered_record = FALSE;
+ if (isset($alias_record['root'])) {
+ // If the alias record does not have a defined 'databases' entry,
+ // then we'll need to look one up
+ if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
+ $values = drush_invoke_process($alias_record, "sql-conf", array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE));
+ if (is_array($values) && ($values['error_status'] == 0)) {
+ $altered_record = TRUE;
+ // If there are any special settings in the '@self' record returned by drush_invoke_process,
+ // then add those into our altered record as well
+ if (array_key_exists('self', $values)) {
+ $alias_record = array_merge($values['self'], $alias_record);
+ }
+ drush_sitealias_cache_db_settings($alias_record, $values['object']);
+ }
+ }
+ }
+ return $altered_record;
+}
+
+function drush_sitealias_cache_db_settings(&$alias_record, $databases) {
+ if (!empty($databases)) {
+ $alias_record['databases'] = $databases;
+ }
+
+ // If the name is set, then re-cache the record after we fetch the databases
+ if (array_key_exists('#name', $alias_record)) {
+ $all_site_aliases =& drush_get_context('site-aliases');
+ $all_site_aliases['@' . $alias_record['#name']] = $alias_record;
+ // Check and see if this record is a copy of 'self'
+ if (($alias_record['#name'] != 'self') && array_key_exists('@self', $all_site_aliases) && array_key_exists('#name', $all_site_aliases['@self']) && ($all_site_aliases['@self']['#name'] == $alias_record['#name'])) {
+ $all_site_aliases['@self'] = $alias_record;
+ }
+ }
+}
+
+/**
+ * Check to see if we have already bootstrapped to a site.
+ */
+function drush_sitealias_is_bootstrapped_site($alias_record) {
+ if (!isset($alias_record['remote-host']) && array_key_exists('root', $alias_record)) {
+ $self_record = drush_sitealias_get_record("@self");
+ if (empty($self_record) || !array_key_exists('root', $self_record)) {
+ // TODO: If we have not bootstrapped to a site yet, we could
+ // perhaps bootstrap to $alias_record here.
+ return FALSE;
+ }
+ elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Determines whether a given site alias is for a remote site.
+ *
+ * @param string $alias
+ * An alias name or site specification.
+ *
+ * @return bool
+ * Returns TRUE if the alias refers to a remote site, FALSE if it does not, or NULL is unsure.
+ */
+function drush_sitealias_is_remote_site($alias) {
+ if (is_array($alias) && !empty($alias['remote-host'])) {
+ return TRUE;
+ }
+ if (!is_string($alias) || !strlen($alias)) {
+ return NULL;
+ }
+
+ $site_record = drush_sitealias_get_record($alias);
+ if ($site_record) {
+ if (!empty($site_record['remote-host'])) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+ }
+ else {
+ drush_set_error('Unrecognized site alias.');
+ }
+}
+
+/**
+ * Get the name of the current bootstrapped site
+ */
+function drush_sitealias_bootstrapped_site_name() {
+ $site_name = NULL;
+ $self_record = drush_sitealias_get_record('@self');
+ if (array_key_exists('#name', $self_record)) {
+ $site_name = $self_record['#name'];
+ }
+ if (!isset($site_name) || ($site_name == '@self')) {
+ $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
+ if (isset($drupal_root)) {
+ $drupal_uri = drush_get_context('DRUSH_SELECTED_URI', 'default');
+ $drupal_uri = str_replace('http://', '', $drupal_uri);
+ // TODO: Maybe use _drush_sitealias_find_local_alias_name?
+ $site_name = $drupal_root . '#' . $drupal_uri;
+ }
+ }
+ return $site_name;
+}
+
+/**
+ * If there are any path aliases (items beginning with "%") in the test
+ * string, then resolve them as path aliases and add them to the provided
+ * alias record.
+ *
+ * @param $alias_record
+ * The full alias record to use in path alias expansion
+ * @param $test_string
+ * A slash-separated list of path aliases to resolve
+ * e.g. "%files/%special".
+ */
+function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') {
+ $path_aliases = array_key_exists('path-aliases', $alias_record) ? $alias_record['path-aliases'] : array();
+ // Convert the test string into an array of items, and
+ // from this make a comma-separated list of projects
+ // that we can pass to 'drush status'.
+ $test_array = explode('/', $test_string);
+ $project_array = array();
+ foreach($test_array as $one_item) {
+ if (!empty($one_item) && ($one_item[0] == '%') && (!array_key_exists($one_item,$path_aliases))) {
+ $project_array[] = substr($one_item,1);
+ }
+ }
+ $project_list = implode(',', $project_array);
+
+ if (!empty($project_array)) {
+ // Optimization: if we're already bootstrapped to the
+ // site specified by $alias_record, then we can just
+ // call _core_site_status_table() rather than use backend invoke.
+ if (drush_sitealias_is_bootstrapped_site($alias_record) && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
+ $status_values = _core_site_status_table($project_list);
+ }
+ else {
+ $values = drush_invoke_process($alias_record, "core-status", array(), empty($project_list) ? array() : array('project' => $project_list), array('integrate' => FALSE, 'override-simulated' => TRUE));
+ $status_values = $values['object'];
+ }
+ if (isset($status_values['%paths'])) {
+ foreach ($status_values['%paths'] as $key => $path) {
+ $alias_record['path-aliases'][$key] = $path;
+ }
+ }
+ // If 'root' is not set in the alias, then fill it in from the status values.
+ if (!isset($alias_record['root']) && isset($status_values['root'])) {
+ $alias_record['root'] = $status_values['root'];
+ }
+ }
+}
+
+/**
+ * Given an alias record that is a site list (contains a 'site-list' entry),
+ * resolve all of the members of the site list and return them
+ * is an array of alias records.
+ *
+ * @param $alias_record
+ * The site list alias record array
+ * @return
+ * An array of individual site alias records
+ */
+function drush_sitealias_resolve_sitelist($alias_record) {
+ $result_list = array();
+ if (isset($alias_record)) {
+ if (array_key_exists('site-list', $alias_record)) {
+ foreach ($alias_record['site-list'] as $sitespec) {
+ $one_result = drush_sitealias_get_record($sitespec);
+ $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result));
+ }
+ }
+ elseif (array_key_exists('#name', $alias_record)) {
+ $result_list[$alias_record['#name']] = $alias_record;
+ }
+ }
+
+ return $result_list;
+}
+
+function _drush_sitelist_find_in_list($one_source, &$target) {
+ $result = FALSE;
+
+ foreach ($target as $key => $one_target) {
+ if(_drush_sitelist_check_site_records($one_source, $one_target)) {
+ $result = $one_target;
+ unset($target[$key]);
+ }
+ }
+
+ return $result;
+}
+
+function _drush_sitelist_check_site_records($source, $target) {
+ if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Initialize an alias record; called as soon as the alias
+ * record is loaded from its alias file, before it is stored
+ * in the cache.
+ *
+ * @param alias_record
+ * The alias record to be initialized; parameter is modified in place.
+ */
+function _drush_sitealias_initialize_alias_record(&$alias_record) {
+ // If there is a 'from-list' entry, then build a derived
+ // list based on the site list with the given name.
+ if (array_key_exists('from-list', $alias_record)) {
+ // danger of infinite loops... move to transient defaults?
+ $from_record = drush_sitealias_get_record($alias_record['from-list']);
+ $from_list = drush_sitealias_resolve_sitelist($from_record);
+ $derived_list = array();
+ foreach ($from_list as $one_record) {
+ $derived_record = _drush_sitealias_derive_record($one_record, $alias_record);
+ $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record);
+ }
+
+ $alias_record = array();
+ if (!empty($derived_list)) {
+ $alias_record['site-list'] = $derived_list;
+ }
+ }
+ // If there is a 'site-search-path' entry, then build
+ // a 'site-list' entry from all of the sites that can be
+ // found in the search path.
+ if (array_key_exists('site-search-path', $alias_record)) {
+ // TODO: Is there any point in merging the sites from
+ // the search path with any sites already listed in the
+ // 'site-list' entry? For now we'll just overwrite.
+ $search_path = $alias_record['site-search-path'];
+ if (!is_array($search_path)) {
+ $search_path = explode(',', $search_path);
+ }
+ $found_sites = _drush_sitealias_find_local_sites($search_path);
+ $alias_record['site-list'] = $found_sites;
+ // The 'unordered-list' flag indicates that the order of the items in the site list is not stable.
+ $alias_record['unordered-list'] = '1';
+ // DEBUG: var_export($alias_record, FALSE);
+ }
+ if (array_key_exists('site-list', $alias_record)) {
+ if (!is_array($alias_record['site-list'])) {
+ $alias_record['site-list'] = explode(',', $alias_record['site-list']);
+ }
+ }
+ else {
+ if (isset($alias_record['root']) && !isset($alias_recort['uri'])) {
+ $alias_recort['uri'] = 'default';
+ }
+ }
+}
+
+/**
+ * Add "static" default values to the given alias record. The
+ * difference between a static default and a transient default is
+ * that static defaults -always- exist in the alias record, and
+ * they are cached, whereas transient defaults are only added
+ * if the given drush command explicitly adds them.
+ *
+ * @param alias_record
+ * An alias record with most values already filled in
+ */
+function _drush_sitealias_add_static_defaults(&$alias_record) {
+ // If there is a 'db-url' entry but not 'databases' entry, then we will
+ // build 'databases' from 'db-url' so that drush commands that use aliases
+ // can always count on using a uniform 'databases' array.
+ if (isset($alias_record['db-url']) && !isset($alias_record['databases'])) {
+ $alias_record['databases'] = drush_sitealias_convert_db_from_db_url($alias_record['db-url']);
+ }
+
+ // Canonicalize paths.
+ if (!empty($alias_record['root'])) {
+ $alias_record['root'] = Path::canonicalize($alias_record['root']);
+ }
+
+ // Adjustments for aliases to drupal instances (as opposed to aliases that are site lists)
+ if (array_key_exists('uri', $alias_record)) {
+ // Make sure that there is always a 'path-aliases' array
+ if (!array_key_exists('path-aliases', $alias_record)) {
+ $alias_record['path-aliases'] = array();
+ }
+ // If there is a 'root' entry, then copy it to the '%root' path alias
+ if (isset($alias_record['root'])) {
+ $alias_record['path-aliases']['%root'] = $alias_record['root'];
+ }
+ }
+}
+
+function _drush_sitealias_derive_record($from_record, $modifying_record) {
+ $result = $from_record;
+
+ // If there is a 'remote-user' in the modifying record, copy it.
+ if (array_key_exists('remote-user', $modifying_record)) {
+ $result['remote-user'] = $from_record['remote_user'];
+ }
+ // If there is a 'remote-host', then:
+ // If it is empty, clear the remote host in the result record
+ // If it ends in '.', then prepend it to the remote host in the result record
+ // Otherwise, copy it to the result record
+ if (array_key_exists('remote-host', $modifying_record)) {
+ $remote_host_modifier = $modifying_record['remote-host'];
+ if(empty($remote_host_modifier)) {
+ unset($result['remote-host']);
+ unset($result['remote-user']);
+ }
+ elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') {
+ $result['remote-host'] = $remote_host_modifier . $result['remote-host'];
+ }
+ else {
+ $result['remote-host'] = $remote_host_modifier;
+ }
+ }
+ // If there is a 'root', then:
+ // If it begins with '/', copy it to the result record
+ // Otherwise, append it to the result record
+ if (array_key_exists('root', $modifying_record)) {
+ $root_modifier = $modifying_record['root'];
+ if($root_modifier[0] == '/') {
+ $result['root'] = $root_modifier;
+ }
+ else {
+ $result['root'] = $result['root'] . '/' . $root_modifier;
+ }
+ }
+ // Poor man's realpath: take out the /../ with preg_replace.
+ // (realpath fails if the files in the path do not exist)
+ while(strpos($result['root'], '/../') !== FALSE) {
+ $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']);
+ }
+
+ // TODO: Should we allow the uri to be transformed?
+ // I think that if the uri does not match, then you should
+ // always build the list by hand, and not rely on '_drush_sitealias_derive_record'.
+
+ return $result;
+}
+
+/**
+ * Convert from an alias record to a site specification
+ *
+ * @param alias_record
+ * The full alias record to convert
+ *
+ * @param with_db
+ * True if the site specification should include a ?db-url term
+ *
+ * @return string
+ * The site specification
+ */
+function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) {
+ $result = '';
+
+ // TODO: we should handle 'site-list' records too.
+ if (array_key_exists('site-list', $alias_record)) {
+ // TODO: we should actually expand the site list and recompose it
+ $result = implode(',', $alias_record['site-list']);
+ }
+ else {
+ // There should always be a uri
+ if (array_key_exists('uri', $alias_record)) {
+ $result = '#' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record));
+ }
+ // There should always be a root
+ if (array_key_exists('root', $alias_record)) {
+ $result = $alias_record['root'] . $result;
+ }
+ if (array_key_exists('remote-host', $alias_record)) {
+ $result = drush_remote_host($alias_record) . $result;
+ }
+
+ // Add the database info to the specification if desired
+ if ($with_db) {
+ // If db-url is not supplied, look it up from the remote
+ // or local site and add it to the site alias
+ if (!isset($alias_record['db-url'])) {
+ drush_sitealias_add_db_url($alias_record);
+ }
+ $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']);
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Search for drupal installations in the search path.
+ *
+ * @param search_path
+ * An array of drupal root folders
+ *
+ * @return
+ * An array of site specifications (/path/to/root#sitename.com)
+ */
+function _drush_sitealias_find_local_sites($search_path) {
+ $result = array();
+ foreach ($search_path as $a_drupal_root) {
+ $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root));
+ }
+ return $result;
+}
+
+/**
+ * Return a list of all of the local sites at the specified drupal root.
+ */
+function _drush_find_local_sites_at_root($a_drupal_root = '', $search_depth = 1) {
+ $site_list = array();
+ $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root );
+ if (!empty($base_path)) {
+ if (drush_valid_root($base_path)) {
+ // If $a_drupal_root is in fact a valid drupal root, then return
+ // all of the sites found inside the 'sites' folder of this drupal instance.
+ $site_list = _drush_find_local_sites_in_sites_folder($base_path);
+ }
+ else {
+ $bootstrap_files = drush_scan_directory($base_path, '/' . basename(DRUSH_DRUPAL_SIGNATURE) . '/' , array('.', '..', 'CVS', 'examples'), 0, drush_get_option('search-depth', $search_depth) + 1, 'filename', 1);
+ foreach ($bootstrap_files as $one_bootstrap => $info) {
+ $includes_dir = dirname($one_bootstrap);
+ if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_SIGNATURE))) {
+ $drupal_root = dirname($includes_dir);
+ $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list);
+ }
+ }
+ }
+ }
+ return $site_list;
+}
+
+/**
+ * Return a list of all of the local sites at the specified 'sites' folder.
+ */
+function _drush_find_local_sites_in_sites_folder($a_drupal_root) {
+ $site_list = array();
+
+ // If anyone searches for sites at a given root, then
+ // make sure that alias files stored at this root
+ // directory are included in the alias search path
+ drush_sitealias_add_to_alias_path($a_drupal_root);
+
+ $base_path = $a_drupal_root . '/sites';
+
+ // TODO: build a cache keyed off of $base_path (realpath($base_path)?),
+ // so that it is guarenteed that the lists returned will definitely be
+ // exactly the same should this routine be called twice with the same path.
+
+ $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1, 'filename', 1);
+ foreach ($files as $filename => $info) {
+ if ($info->basename == 'settings.php') {
+ // First we'll resolve the realpath of the settings.php file,
+ // so that we get the correct drupal root when symlinks are in use.
+ $real_sitedir = dirname(realpath($filename));
+ $real_root = drush_locate_root($filename);
+ if ($real_root !== FALSE) {
+ $a_drupal_site = $real_root . '#' . basename($real_sitedir);
+ }
+ // If the symlink points to some folder outside of any drupal
+ // root, then we'll use the
+ else {
+ $uri = drush_sitealias_site_dir_from_filename($filename);
+ $a_drupal_site = $a_drupal_root . '#' . $uri;
+ }
+ // Add the site if it isn't already in the array
+ if (!in_array($a_drupal_site, $site_list)) {
+ $site_list[] = $a_drupal_site;
+ }
+ }
+ }
+ return $site_list;
+}
+
+function drush_sitealias_create_sites_alias($a_drupal_root = '') {
+ $sites_list = _drush_find_local_sites_at_root($a_drupal_root);
+ _drush_sitealias_cache_alias('@sites', array('site-list' => $sites_list));
+}
+
+/**
+ * Add "transient" default values to the given alias record. The
+ * difference between a static default and a transient default is
+ * that static defaults -always- exist in the alias record,
+ * whereas transient defaults are only added if the given drush
+ * command explicitly calls this function. The other advantage
+ * of transient defaults is that it is possible to differentiate
+ * between a default value and an unspecified value, since the
+ * transient defaults are not added until requested.
+ *
+ * Since transient defaults are not cached, you should avoid doing
+ * expensive operations here. To be safe, drush commands should
+ * avoid calling this function more than once.
+ *
+ * @param alias_record
+ * An alias record with most values already filled in
+ */
+function _drush_sitealias_add_transient_defaults(&$alias_record) {
+ if (isset($alias_record['path-aliases'])) {
+ // Add the path to the drush folder to the path aliases as !drush
+ if (!array_key_exists('%drush', $alias_record['path-aliases'])) {
+ if (array_key_exists('%drush-script', $alias_record['path-aliases'])) {
+ $alias_record['path-aliases']['%drush'] = dirname($alias_record['path-aliases']['%drush-script']);
+ }
+ else {
+ $alias_record['path-aliases']['%drush'] = dirname(drush_find_drush());
+ }
+ }
+ // Add the path to the site folder to the path aliases as !site
+ if (!array_key_exists('%site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) {
+ $alias_record['path-aliases']['%site'] = 'sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record)) . '/';
+ }
+ }
+}
+
+/**
+ * Find the name of a local alias record that has the specified
+ * root and uri.
+ */
+function _drush_sitealias_find_local_alias_name($root, $uri) {
+ $result = '';
+ $all_site_aliases =& drush_get_context('site-aliases');
+
+ foreach ($all_site_aliases as $alias_name => $alias_values) {
+ if (!array_key_exists('remote-host', $alias_values) && array_key_exists('root', $alias_values) && array_key_exists('uri', $alias_values) && ($alias_name != '@self')) {
+ if (($root == $alias_values['root']) && ($uri == $alias_values['uri'])) {
+ $result = $alias_name;
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * If '$alias' is the name of a folder in the sites folder of the given drupal
+ * root, then build an alias record for it
+ *
+ * @param alias
+ * The name of the site in the 'sites' folder to convert
+ * @return array
+ * An alias record, or empty if none found.
+ */
+function _drush_sitealias_find_record_for_local_site($alias, $drupal_root = NULL) {
+ $alias_record = array();
+
+ // Clip off the leading '#' if it is there
+ if (substr($alias,0,1) == '#') {
+ $alias = substr($alias,1);
+ }
+
+ if (!isset($drupal_root)) {
+ $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
+ }
+
+ if (!empty($drupal_root)) {
+ $alias_dir = drush_sitealias_uri_to_site_dir($alias, $drupal_root);
+ $site_settings_file = $drupal_root . '/sites/' . $alias_dir . '/settings.php';
+ $alias_record = drush_sitealias_build_record_from_settings_file($site_settings_file, $alias, $drupal_root);
+ }
+
+ return $alias_record;
+}
+
+function drush_sitealias_build_record_from_settings_file($site_settings_file, $alias = null, $drupal_root = null) {
+ $alias_record = array();
+
+ if (file_exists($site_settings_file)) {
+ if (!isset($drupal_root)) {
+ $drupal_root = drush_locate_root($site_settings_file);
+ }
+
+ $alias_record['root'] = $drupal_root;
+ if (isset($alias)) {
+ $alias_record['uri'] = $alias;
+ }
+ else {
+ $alias_record['uri'] = _drush_sitealias_site_dir_to_uri(drush_sitealias_site_dir_from_filename($site_settings_file));
+ }
+ }
+
+ return $alias_record;
+}
+
+/**
+ * Pull the site directory from the path to settings.php
+ *
+ * @param site_settings_file
+ * path to settings.php
+ *
+ * @return string
+ * the site directory component of the path to settings.php
+ */
+function drush_sitealias_site_dir_from_filename($site_settings_file) {
+ return basename(dirname($site_settings_file));
+}
+
+/**
+ * Convert from a URI to a site directory.
+ *
+ * @param uri
+ * A uri, such as http://domain.com:8080/drupal
+ * @return string
+ * A directory, such as domain.com.8080.drupal
+ */
+function drush_sitealias_uri_to_site_dir($uri, $site_root = NULL) {
+ $uri = str_replace('http://', '', $uri);
+ $uri = str_replace('https://', '', $uri);
+ if (drush_is_windows()) {
+ // Handle absolute paths on windows
+ $uri = str_replace(array(':/', ':\\'), array('.', '.'), $uri);
+ }
+
+ $hostname = str_replace(array('/', ':', '\\'), array('.', '.', '.'), $uri);
+
+ // Check sites.php mappings
+ $site_dir = drush_site_dir_lookup_from_hostname($hostname, $site_root);
+
+ return $site_dir ? $site_dir : $hostname;
+}
+
+/**
+ * Convert from an old-style database URL to an array of database settings.
+ *
+ * @param db_url
+ * A Drupal 6 db url string to convert, or an array with a 'default' element.
+ * @return array
+ * An array of database values containing only the 'default' element of
+ * the db url. If the parse fails the array is empty.
+ */
+function drush_convert_db_from_db_url($db_url) {
+ $db_spec = array();
+
+ if (is_array($db_url)) {
+ $db_url_default = $db_url['default'];
+ }
+ else {
+ $db_url_default = $db_url;
+ }
+
+ // If it's a sqlite database, pick the database path and we're done.
+ if (strpos($db_url_default, 'sqlite://') === 0) {
+ $db_spec = array(
+ 'driver' => 'sqlite',
+ 'database' => substr($db_url_default, strlen('sqlite://')),
+ );
+ }
+ else {
+ $url = parse_url($db_url_default);
+ if ($url) {
+ // Fill in defaults to prevent notices.
+ $url += array(
+ 'scheme' => NULL,
+ 'user' => NULL,
+ 'pass' => NULL,
+ 'host' => NULL,
+ 'port' => NULL,
+ 'path' => NULL,
+ );
+ $url = (object)array_map('urldecode', $url);
+ $db_spec = array(
+ 'driver' => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme,
+ 'username' => $url->user,
+ 'password' => $url->pass,
+ 'host' => $url->host,
+ 'port' => $url->port,
+ 'database' => ltrim($url->path, '/'),
+ );
+ }
+ }
+
+ return $db_spec;
+}
+
+/**
+ * Convert from an old-style database URL to an array of database settings
+ *
+ * @param db_url
+ * A Drupal 6 db-url string to convert, or an array with multiple db-urls.
+ * @return array
+ * An array of database values.
+ */
+function drush_sitealias_convert_db_from_db_url($db_url) {
+ $result = array();
+
+ if (!is_array($db_url)) {
+ $result = array('default' => array('default' => drush_convert_db_from_db_url($db_url)));
+ }
+ else {
+ foreach ($db_url as $one_name => $one_db_url) {
+ $result[$one_name] = array('default' => drush_convert_db_from_db_url($one_db_url));
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * Utility function used by drush_get_alias; keys that start with
+ * '%' or '!' are path aliases, the rest are entries in the alias record.
+ */
+function _drush_sitealias_set_record_element(&$alias_record, $key, $value) {
+ if ((substr($key,0,1) == '%') || (substr($key,0,1) == '!')) {
+ $alias_record['path-aliases'][$key] = $value;
+ }
+ elseif (!empty($key)) {
+ $alias_record[$key] = $value;
+ }
+}
+
+/**
+ * Looks up the specified alias record and calls through to
+ * drush_sitealias_set_alias_context, below.
+ *
+ * @param alias
+ * The name of the alias record
+ * @param prefix
+ * The prefix value to afix to the beginning of every
+ * key set.
+ * @return boolean
+ * TRUE is an alias was found and processed.
+ */
+function _drush_sitealias_set_context_by_name($alias, $prefix = '') {
+ if ($alias) {
+ $site_alias_settings = drush_sitealias_get_record($alias);
+ if (!empty($site_alias_settings)) {
+ drush_sitealias_set_alias_context($site_alias_settings, $prefix);
+ drush_sitealias_cache_alias_by_path($site_alias_settings);
+ if (empty($prefix)) {
+
+ // Create an alias '@self'
+ // Allow 'uri' from the commandline to override
+ $drush_uri = drush_get_option(array('uri', 'l'), FALSE);
+ if ($drush_uri) {
+ $site_alias_settings['uri'] = $drush_uri;
+ }
+
+ _drush_sitealias_cache_alias('@self', $site_alias_settings);
+ // Change the selected site to match the new --root and --uri, if any were set.
+ _drush_preflight_root_uri();
+ }
+ return $site_alias_settings;
+ }
+ }
+ return array();
+}
+
+/**
+ * Given an alias record, overwrite its values with options
+ * from the command line and other drush contexts as specified
+ * by the provided prefix. For example, if the prefix is 'source-',
+ * then any option 'source-foo' will set the value 'foo' in the
+ * alias record.
+ */
+function drush_sitealias_overlay_options($site_alias_record, $prefix) {
+ return array_merge($site_alias_record, drush_get_merged_prefixed_options($prefix));
+}
+
+/**
+ * First return an option set via drush_sitealias_overlay_options, if
+ * any, then fall back on "%" . $option from the path aliases.
+ */
+function drush_sitealias_get_path_option($site_alias_record, $option, $default = NULL) {
+ if (isset($site_alias_record) && array_key_exists($option, $site_alias_record)) {
+ return $site_alias_record[$option];
+ }
+ if (isset($site_alias_record) && array_key_exists('path-aliases', $site_alias_record) && array_key_exists("%$option", $site_alias_record['path-aliases'])) {
+ return $site_alias_record['path-aliases']["%$option"];
+ }
+ else {
+ return drush_get_option($option, $default);
+ }
+}
+
+/**
+ * Given a site alias record, copy selected fields from it
+ * into the drush 'alias' context. The 'alias' context has
+ * lower precedence than the 'cli' context, so values
+ * set by an alias record can be overridden by command-line
+ * parameters.
+ *
+ * @param site_alias_settings
+ * An alias record
+ * @param prefix
+ * The prefix value to affix to the beginning of every
+ * key set. For example, if this function is called once with
+ * 'source-' and again with 'destination-' prefixes, then the
+ * source database records will be stored in 'source-databases',
+ * and the destination database records will be in
+ * 'destination-databases'.
+ */
+function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') {
+ $options = drush_get_context('alias');
+
+ // There are some items that we should just skip
+ $skip_list = drush_get_special_keys();
+ // If 'php-options' are set in the alias, then we will force drush
+ // to redispatch via the remote dispatch mechanism even if the target is localhost.
+ if ((array_key_exists('php-options', $site_alias_settings) || array_key_exists('php', $site_alias_settings)) && !drush_get_context('DRUSH_BACKEND', FALSE)) {
+ if (!array_key_exists('remote-host', $site_alias_settings)) {
+ $site_alias_settings['remote-host'] = 'localhost';
+ }
+ }
+ // If 'php-options' are not set in the alias, then skip 'remote-host'
+ // and 'remote-user' if 'remote-host' is actually the local machine.
+ // This prevents drush from using the remote dispatch mechanism (the command
+ // is just run directly on the local machine, bootstrapping to the specified alias)
+ elseif (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) {
+ $skip_list[] = 'remote-host';
+ $skip_list[] = 'remote-user';
+ }
+ // If prefix is set, then copy from the 'prefix-' version
+ // of the drush special keys ('command-specific', 'path-aliases')
+ // into the ordinary version. This will allow us to set
+ // 'source-command-specific' options that will only apply when
+ // the alias is used as the source option for rsync or sql-sync.
+ if (!empty($prefix)) {
+ $special_contexts = drush_get_special_keys();
+ foreach ($special_contexts as $option_name) {
+ if (array_key_exists($prefix . $option_name, $site_alias_settings)) {
+ $site_alias_settings[$option_name] = array_key_exists($option_name, $site_alias_settings) ? array_merge($site_alias_settings[$option_name], $site_alias_settings[$prefix . $option_name]) : $site_alias_settings[$prefix . $option_name];
+ }
+ }
+ }
+ // Transfer all options from the site alias to the drush options
+ // in the 'alias' context.
+ foreach ($site_alias_settings as $key => $value) {
+ // Special handling for path aliases:
+ if ($key == "path-aliases") {
+ $path_aliases = $value;
+ foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) {
+ if (array_key_exists($path_key, $path_aliases)) {
+ // Evaluate the path value, and substitute any path references found.
+ // ex: '%dump-dir' => '%root/dumps' will store sql-dumps in the folder
+ // 'dumps' in the Drupal root folder for the site.
+ $evaluated_path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path_aliases[$path_key]);
+ $options[$prefix . substr($path_key, 1)] = $evaluated_path;
+ }
+ }
+ }
+ // Special handling for command-specific
+ elseif ($key == "command-specific") {
+ $options[$key] = $value;
+ }
+ elseif (!in_array($key, $skip_list)) {
+ $options[$prefix . $key] = $value;
+ }
+ }
+ drush_set_config_options('alias', $options);
+}
+
+/**
+ * Call prior to drush_sitealias_evaluate_path to insure
+ * that any site-specific aliases associated with any
+ * local site in $path are defined.
+ */
+function _drush_sitealias_preflight_path($path) {
+ $alias = NULL;
+ // Parse site aliases if there is a colon in the path
+ // We allow:
+ // @alias:/path
+ // machine.domain.com:/path
+ // machine:/path
+ // Note that paths in the form "c:/path" are converted to
+ // "/cygdrive/c/path" later; we do not want them to confuse
+ // us here, so we skip paths that start with a single character
+ // before the colon if we are running on Windows. Single-character
+ // machine names are allowed in Linux only.
+ $colon_pos = strpos($path, ':');
+ if ($colon_pos > (drush_is_windows("LOCAL") ? 1 : 0)) {
+ $alias = substr($path, 0, $colon_pos);
+ $path = substr($path, $colon_pos + 1);
+ $site_alias_settings = drush_sitealias_get_record($alias);
+ if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
+ return NULL;
+ }
+ $machine = $alias;
+ }
+ else {
+ $machine = '';
+ // if the path is a site alias or a local site...
+ $site_alias_settings = drush_sitealias_get_record($path);
+ if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
+ return NULL;
+ }
+ if (!empty($site_alias_settings) || drush_is_local_host($path)) {
+ $alias = $path;
+ $path = '';
+ }
+ }
+ return array('alias' => $alias, 'path' => $path, 'machine' => $machine);
+}
+
+/**
+ * Given a properly-escaped options string, replace any occurance of
+ * %files and so on embedded inside it with its corresponding path.
+ */
+function drush_sitealias_evaluate_paths_in_options($option_string) {
+ $path_aliases = _core_path_aliases();
+ return str_replace(array_keys($path_aliases), array_values($path_aliases), $option_string);
+}
+
+/**
+ * Evaluate a path from its shorthand form to a literal path
+ * usable by rsync.
+ *
+ * A path is "machine:/path" or "machine:path" or "/path" or "path".
+ * 'machine' might instead be an alias record, or the name
+ * of a site in the 'sites' folder. 'path' might be (or contain)
+ * '%root' or some other path alias. This function will examine
+ * all components of the path and evaluate them as necessary to
+ * come to the final path.
+ *
+ * @param path
+ * The path to evaluate
+ * @param additional_options
+ * An array of options that overrides whatever was passed in on
+ * the command line (like the 'process' context, but only for
+ * the scope of this one call).
+ * @param local_only
+ * If TRUE, force an error if the provided path points to a remote
+ * machine.
+ * @param os
+ * This should be the local system os, unless evaluate path is
+ * being called for rsync, in which case it should be "CWRSYNC"
+ * if cwrsync is being used, or "rsync" to automatically select
+ * between "LOCAL" and "CWRSYNC" based on the platform.
+ * @return
+ * The site record for the machine specified in the path, if any,
+ * with the path to pass to rsync (including the machine specifier)
+ * in the 'evaluated-path' item.
+ */
+function drush_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE, $os = NULL, $command_specific_prefix = '') {
+ $site_alias_settings = array();
+ $path_aliases = array();
+ $remote_user = '';
+
+ $preflight = _drush_sitealias_preflight_path($path);
+ if (!isset($preflight)) {
+ return NULL;
+ }
+
+ $alias = $preflight['alias'];
+ $path = $preflight['path'];
+ $machine = $preflight['machine'];
+
+ if (isset($alias)) {
+ // Note that the alias settings may have an 'os' component, but we do
+ // not want to use it here. The paths passed to rsync should always be
+ // escaped per the LOCAL rules, without regard to the remote platform type.
+ $site_alias_settings = drush_sitealias_get_record($alias);
+ if (!empty($command_specific_prefix)) {
+ drush_command_set_command_specific_options($command_specific_prefix);
+ drush_sitealias_command_default_options($site_alias_settings, $command_specific_prefix);
+ }
+ }
+
+ if (!empty($site_alias_settings)) {
+ if ($local_only && array_key_exists('remote-host', $site_alias_settings)) {
+ return drush_set_error('DRUSH_REMOTE_SITE_IN_LOCAL_CONTEXT', dt("A remote site alias was used in a context where only a local alias is appropriate."));
+ }
+
+ // Apply any options from this alias that might affect our rsync
+ drush_sitealias_set_alias_context($site_alias_settings);
+
+ // Use 'remote-host' from settings if available; otherwise site is local
+ if (drush_sitealias_is_remote_site($site_alias_settings)) {
+ $machine = drush_remote_host($site_alias_settings);
+ }
+ else {
+ $machine = '';
+ }
+ }
+ else {
+ // Strip the machine portion of the path if the
+ // alias points to the local machine.
+ if (drush_is_local_host($machine)) {
+ $machine = '';
+ }
+ else {
+ $machine = "$remote_user$machine";
+ }
+ }
+
+ // TOD: The code below is a little rube-goldberg-ish, and needs to be
+ // reworked. core-rsync will call this function twice: once to
+ // evaluate the destination, and then again to evaluate the source. Things
+ // get odd with --exclude-paths, especially in conjunction with command-specific
+ // and the --exclude-files option. @see testCommandSpecific()
+
+ // If the --exclude-other-sites option is specified, then
+ // convert that into --include-paths='%site' and --exclude-sites.
+ if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_context('exclude-other-sites-processed', FALSE)) {
+ $include_path_option = drush_get_option_override($additional_options, 'include-paths', '');
+ $additional_options['include-paths'] = '%site';
+ if (!empty($include_path_option)) {
+ // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR.
+ $additional_options['include-paths'] .= PATH_SEPARATOR . $include_path_option;
+ }
+ $additional_options['exclude-sites'] = TRUE;
+ drush_set_context('exclude-other-sites-processed', TRUE);
+ }
+ else {
+ unset($additional_options['include-paths']);
+ }
+ // If the --exclude-files option is specified, then
+ // convert that into --exclude-paths='%files'.
+ if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) {
+ $exclude_path_option = drush_get_option_override($additional_options, 'exclude-paths', '');
+ $additional_options['exclude-paths'] = '%files';
+ if (!empty($exclude_path_option)) {
+ // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR.
+ $additional_options['exclude-paths'] .= PATH_SEPARATOR . $exclude_path_option;
+ }
+ $additional_options['exclude-files-processed'] = TRUE;
+ }
+ else {
+ unset($additional_options['exclude-paths']);
+ }
+
+ // If there was no site specification given, and the
+ // machine is local, then try to look
+ // up an alias record for the default drush site.
+ if (empty($site_alias_settings) && empty($machine)) {
+ $drush_uri = drush_get_context('DRUSH_SELECTED_URI', 'default');
+ $site_alias_settings = drush_sitealias_get_record($drush_uri);
+ }
+
+ // Always add transient defaults
+ _drush_sitealias_add_transient_defaults($site_alias_settings);
+
+ // The $resolve_path variable is used by drush_sitealias_resolve_path_references
+ // to test to see if there are any path references such as %site or %files
+ // in it, so that resolution is only done if the path alias is referenced.
+ // Therefore, we can concatenate without worrying too much about the structure of
+ // this variable's contents.
+ $include_path = drush_get_option_override($additional_options, 'include-paths', '');
+ $exclude_path = drush_get_option_override($additional_options, 'exclude-paths', '');
+ if (is_array($include_path)) {
+ $include_path = implode('/', $include_path);
+ }
+ if (is_array($exclude_path)) {
+ $include_path = implode('/', $exclude_path);
+ }
+ $resolve_path = "$path/$include_path/$exclude_path";
+ // Resolve path aliases such as %files, if any exist in the path
+ if (!empty($resolve_path)) {
+ drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path);
+ }
+
+ if (array_key_exists('path-aliases', $site_alias_settings)) {
+ $path_aliases = $site_alias_settings['path-aliases'];
+ }
+
+ // Get the 'root' setting from the alias; if it does not
+ // exist, then get the root from the bootstrapped site.
+ if (array_key_exists('root', $site_alias_settings)) {
+ $drupal_root = $site_alias_settings['root'];
+ }
+ elseif (!drush_sitealias_is_remote_site($site_alias_settings)) {
+ drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
+ $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
+ }
+ if (empty($drupal_root)) {
+ $drupal_root = '';
+ }
+ else {
+ // Add a slash to the end of the drupal root, as below.
+ $drupal_root = drush_trim_path($drupal_root) . "/";
+ }
+ $full_path_aliases = $path_aliases;
+ foreach ($full_path_aliases as $key => $value) {
+ // Expand all relative path aliases to be based off of the Drupal root
+ if (!drush_is_absolute_path($value, "LOCAL") && ($key != '%root')) {
+ $full_path_aliases[$key] = $drupal_root . $value;
+ }
+ // We do not want slashes on the end of our path aliases.
+ $full_path_aliases[$key] = drush_trim_path($full_path_aliases[$key]);
+ }
+
+ // Fill in path aliases in the path, the include path and the exclude path.
+ $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path);
+ if (!empty($include_path)) {
+ drush_set_option('include-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path));
+ }
+ if (!empty($exclude_path)) {
+ drush_set_option('exclude-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path));
+ }
+ // Next make the rsync path, which includes the machine
+ // and path components together.
+ // First make empty paths or relative paths start from the drupal root.
+ if (empty($path) || (!drush_is_absolute_path($path, "LOCAL"))) {
+ $path = $drupal_root . $path;
+ }
+ // When calculating a path for use with rsync, we must correct
+ // absolute paths in the form c:\path when cwrsync is in use.
+ $path = drush_correct_absolute_path_for_exec($path, $os);
+
+ // If there is a $machine component, to the path, then
+ // add it to the beginning
+ $evaluated_path = drush_escapeshellarg($path, $os);
+ if (!empty($machine)) {
+ $evaluated_path = $machine . ':' . $evaluated_path;
+ }
+
+ //
+ // Add our result paths:
+ //
+ // evaluated-path: machine:/path
+ // server-component: machine
+ // path-component: :/path
+ // path: /path
+ // user-path: path (as specified in input parameter)
+ //
+ $site_alias_settings['evaluated-path'] = $evaluated_path;
+ if (!empty($machine)) {
+ $site_alias_settings['server-component'] = $machine;
+ }
+ $site_alias_settings['path-component'] = (!empty($path) ? ':' . $path : '');
+ $site_alias_settings['path'] = $path;
+ $site_alias_settings['user-path'] = $preflight['path'];
+
+ return $site_alias_settings;
+}
+
+/**
+ * Option keys used for site selection.
+ */
+function drush_sitealias_site_selection_keys() {
+ return array('remote-host', 'remote-user', 'ssh-options', '#name', 'os');
+}
+
+
+function sitealias_find_local_drupal_root($site_list) {
+ $drupal_root = NULL;
+
+ foreach ($site_list as $site) {
+ if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) {
+ $drupal_root = $site['root'];
+ }
+ }
+
+ return $drupal_root;
+}
+
+
+/**
+ * Helper function to obtain the keys' names that need special handling in certain
+ * cases.
+ * @return
+ * A non-associative array containing the needed keys' names.
+ */
+function drush_get_special_keys() {
+ $special_keys = array(
+ 'command-specific',
+ 'site-aliases',
+ );
+ return $special_keys;
+}
+
+/**
+ * Read the tmp file where the persistent site setting is stored.
+ *
+ * @return string
+ * A valid site specification.
+ */
+function drush_sitealias_site_get() {
+ if (($filename = drush_sitealias_get_envar_filename()) && file_exists($filename)) {
+ $site = file_get_contents($filename);
+ return $site;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Un-set the currently use'd site alias.
+ */
+function drush_sitealias_site_clear() {
+ if ($filename = drush_sitealias_get_envar_filename()) {
+ return drush_delete_dir($filename);
+ }
+ return FALSE;
+}
+
+/**
+ * Returns the filename for the file that stores the DRUPAL_SITE variable.
+ *
+ * @param string $filename_prefix
+ * An arbitrary string to prefix the filename with.
+ *
+ * @return string|false
+ * Returns the full path to temp file if possible, or FALSE if not.
+ */
+function drush_sitealias_get_envar_filename($filename_prefix = 'drush-drupal-site-') {
+ $shell_pid = getenv('DRUSH_SHELL_PID');
+ if (!$shell_pid && function_exists('posix_getppid')) {
+ $shell_pid = posix_getppid();
+ }
+ if (!$shell_pid) {
+ return FALSE;
+ }
+
+ $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp';
+ $username = drush_get_username();
+
+ return "{$tmp}/drush-env-{$username}/{$filename_prefix}" . $shell_pid;
+}
+
+/**
+ * Cache the specified alias in the alias path cache. The
+ * alias path cache creates a lookup from the site folder
+ * (/path/to/drupal/sites/default) to the provided alias record.
+ *
+ * Only the name of the alias and the path to the file it
+ * is stored in is cached; when it is retrieved, it is
+ * loaded directly from the correct file.
+ */
+function drush_sitealias_cache_alias_by_path($alias_record) {
+ if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) {
+ $path = drush_sitealias_local_site_path($alias_record);
+ if ($path) {
+ $cid = drush_get_cid('alias-path-', array(), array($path));
+ $alias_path_data = array(
+ '#name' => $alias_record['#name'],
+ '#file' => $alias_record['#file'],
+ );
+ drush_cache_set($cid, $alias_path_data);
+ }
+ }
+}
+
+/**
+ * Look for a defined alias that points to the specified
+ * site directory. The cache is tested first; if nothing
+ * is cached, then an exhaustive search is done for the
+ * specified site. If the exhaustive search returns a
+ * match, then it is cached.
+ *
+ * @param $path
+ * /path/to/drupal/sites/default
+ * @return
+ * An alias record for the provided path
+ */
+function drush_sitealias_lookup_alias_by_path($path, $allow_best_match=FALSE) {
+ $result = drush_sitealias_quick_lookup_cached_alias_by_path($path);
+ $fallback = array();
+ if (empty($result)) {
+ $aliases = _drush_sitealias_find_and_load_all_aliases();
+ foreach ($aliases as $name => $alias_record) {
+ if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) {
+ if ($path == drush_sitealias_local_site_path($alias_record)) {
+ $result = $alias_record;
+ break;
+ }
+ if (substr($path, 0, strlen($alias_record['root'])) == $alias_record['root']) {
+ $fallback = $alias_record;
+ }
+ }
+ }
+ }
+ if (empty($result) && $allow_best_match) {
+ $result = $fallback;
+ }
+ if (!empty($result)) {
+ _drush_sitealias_add_inherited_values_to_record($result);
+ drush_sitealias_cache_alias_by_path($result);
+ }
+ return $result;
+}
+
+/**
+ * Look for a cached alias that points to the specified
+ * site directory. Nothing is returned if there is no
+ * matching cached alias.
+ *
+ * @param $path
+ * /path/to/drupal/sites/default
+ * @return
+ * An alias record for the provided path
+ */
+function drush_sitealias_quick_lookup_cached_alias_by_path($path) {
+ $alias_record = array();
+ $cid = drush_get_cid('alias-path-', array(), array($path));
+ $alias_path_cache = drush_cache_get($cid);
+ if (isset($alias_path_cache->data)) {
+ $alias_name = $alias_path_cache->data['#name'];
+ $alias_file = $alias_path_cache->data['#file'];
+
+ $alias_record = _drush_sitealias_find_and_load_alias_from_file($alias_name, array($alias_file));
+ _drush_sitealias_add_inherited_values_to_record($alias_record);
+ $alias_record['#name'] = $alias_name;
+ }
+ return $alias_record;
+}
+
+/**
+ * Return the site root, if there is one in the record.
+ */
+function drush_sitealias_get_root($alias_record) {
+ return array_key_exists('root', $alias_record) ? $alias_record['root'] : NULL;
+}
+
+/**
+ * Decide on which side to run a core-rsync.
+ *
+ * @param $source
+ * @param $destination
+ * @param $runner Where to run the rsync operation: 'destination', 'source',
+ * 'auto' ('destination' if both are remote, otherwise '@self') or FALSE (@self)
+ * @return mixed
+ */
+function drush_get_runner($source, $destination, $runner = FALSE) {
+ if (is_string($source)) {
+ $source = drush_sitealias_get_record($site);
+ }
+ if (is_string($destination)) {
+ $destination = drush_sitealias_get_record($destination);
+ }
+
+ // If both sites are remote, and --runner=auto, then we'll use the destination site.
+ if (drush_sitealias_is_remote_site($source) && drush_sitealias_is_remote_site($destination)) {
+ if ($runner == 'auto') {
+ $runner = 'destination';
+ }
+ }
+
+ // If the user explicitly requests a remote site, then return the selected one.
+ if ($runner == 'destination') {
+ return "@" . $destination['#name'];
+ }
+ if ($runner == 'source') {
+ return "@" . $source['#name'];
+ }
+
+ // Default to running rsync locally. When in doubt, local is best, because
+ // we can always resolve aliases here.
+ return '@self';
+}