$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'; }