fce233d81120e2581d5e037f41a55aa72aad1fea
[yaffs-website] / vendor / drush / drush / includes / sitealias.inc
1 <?php
2
3 /**
4  * @file
5  * The site alias API.
6  *
7  * Run commands on remote server(s).
8  * @see example.aliases.drushrc.php
9  * @see http://drupal.org/node/670460
10  */
11
12 use Drush\Log\LogLevel;
13 use Webmozart\PathUtil\Path;
14
15 /**
16  * Check to see if the user specified an alias
17  * in an arguement, or via site-set.  If so, return
18  * the name of the alias.
19  *
20  * If the alias came from args, then remove it
21  * from args.
22  */
23 function drush_sitealias_check_arg_and_site_set() {
24   $args = drush_get_arguments();
25   $target_alias = FALSE;
26
27   // Test to see if the first arg is a valid alias identifier.
28   // If the first arguement is a well-formed identifier, but we
29   // cannot find a record for it, then we will fail with an error.
30   if (!empty($args) && drush_sitealias_valid_alias_format($args[0])) {
31     // Pop the alias off the arguments list first thing.
32     $target_alias = array_shift($args);
33     drush_set_arguments($args);
34   }
35   else {
36     // If the user did not specify an alias via an argument,
37     // check to see if a site env was set.
38     $target_alias = drush_sitealias_site_get();
39   }
40
41   // Record the user's desired target alias name
42   if ($target_alias) {
43     drush_set_context('DRUSH_TARGET_SITE_ALIAS', $target_alias);
44   }
45   return $target_alias;
46 }
47
48 /**
49  * Check to see if the first command-line arg or the
50  * -l option is a site alias; if it is, copy its record
51  * values to the 'alias' context.
52  *
53  * @return boolean
54  *   TRUE if a site alias was found and processed.
55  */
56 function drush_sitealias_check_arg() {
57   $args = drush_get_arguments();
58
59   // Test to see if the first arg is a site specification
60   if (!empty($args) && _drush_sitealias_set_context_by_name($args[0])) {
61     drush_set_context('DRUSH_TARGET_SITE_ALIAS', $args[0]);
62     array_shift($args);
63     // We only need to expand the site specification
64     // once, then we are done.
65     drush_set_arguments($args);
66     return TRUE;
67   }
68   // Return false to indicate that no site alias was specified.
69   return FALSE;
70 }
71
72 /*
73  * Check to see if user has selected a site via site-set command.
74  */
75 function drush_sitealias_check_site_env() {
76   $site = drush_get_context('DRUSH_TARGET_SITE_ALIAS');
77   if (empty($site)) {
78     $site_env = drush_sitealias_site_get();
79     if (!empty($site_env) && (_drush_sitealias_set_context_by_name($site_env))) {
80       drush_set_context('DRUSH_TARGET_SITE_ALIAS', $site_env);
81       return TRUE;
82     }
83   }
84   // Return false to indicate that no site alias was specified.
85   return FALSE;
86 }
87
88 /**
89  * Check to see if a '@self' record was created during bootstrap.
90  * If not, make one now.
91  */
92 function drush_sitealias_create_self_alias() {
93   $self_record = drush_sitealias_get_record('@self');
94   if (!array_key_exists('root', $self_record) && !array_key_exists('remote-host', $self_record)) {
95     $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
96     $uri = drush_get_context('DRUSH_SELECTED_URI');
97     if (!empty($drupal_root) && !empty($uri)) {
98       // Create an alias '@self'
99       _drush_sitealias_cache_alias('@self', array('root' => $drupal_root, 'uri' => $uri));
100     }
101   }
102 }
103
104 /**
105  * Given a list of alias records, shorten the name used if possible
106  */
107 function drush_sitealias_simplify_names($site_list) {
108   $result = array();
109   foreach ($site_list as $original_name => $alias_record) {
110     $adjusted_name = $alias_record['#name'];
111     $hashpos = strpos($original_name, '#');
112     if ($hashpos !== FALSE) {
113       $adjusted_name = substr($original_name, $hashpos);
114       if (array_key_exists('remote-host', $alias_record)) {
115         $adjusted_name = $alias_record['remote-host'] . $adjusted_name;
116       }
117     }
118     $result[$adjusted_name] = $alias_record;
119   }
120   return $result;
121 }
122
123 /**
124  * Given an array of site specifications, resolve each one in turn and
125  * return an array of alias records.  If you only want a single record,
126  * it is preferable to simply call drush_sitealias_get_record() directly.
127  *
128  * @param $site_specifications
129  *   One of:
130  *     A comma-separated list of site specifications: '@site1,@site2'
131  *     An array of site specifications: array('@site1','@site2')
132  *     An array of alias records:
133  *       array(
134  *         'site1' => array('root' => ...),
135  *         'site2' => array('root' => ...)
136  *       )
137  *   An array of site specifications.
138  *   @see drush_sitealias_get_record() for the format of site specifications.
139  * @return
140  *   An array of alias records
141  */
142 function drush_sitealias_resolve_sitespecs($site_specifications, $alias_path_context = NULL) {
143   $result_list = array();
144   $not_found = array();
145   if (!is_array($site_specifications)) {
146     $site_specifications = explode(',', $site_specifications);
147   }
148   if (!empty($site_specifications)) {
149     foreach ($site_specifications as $site) {
150       if (is_array($site)) {
151         $result_list[] = $site;
152       }
153       else {
154         $alias_record = drush_sitealias_get_record($site, $alias_path_context);
155         if (!$alias_record) {
156           $not_found[] = $site;
157         }
158         else {
159           $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record));
160         }
161       }
162     }
163   }
164   return array($result_list, $not_found);
165 }
166
167 /**
168  * Returns TRUE if $alias is a valid format for an alias name.
169  *
170  * Mirrors the allowed formats shown below for drush_sitealias_get_record.
171  */
172 function drush_sitealias_valid_alias_format($alias) {
173   return ( (strpos($alias, ',') !== false) ||
174     ((strpos($alias, '@') === FALSE ? 0 : 1) + (strpos($alias, '/') === FALSE ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) ||
175     ($alias{0} == '#') ||
176     ($alias{0} == '@')
177   );
178 }
179
180 /**
181  * Get a site alias record given an alias name or site specification.
182  *
183  * If it is the name of a site alias, return the alias record from
184  * the site aliases array.
185  *
186  * If it is the name of a folder in the 'sites' folder, construct
187  * an alias record from values stored in settings.php.
188  *
189  * If it is a site specification, construct an alias record from the
190  * values in the specification.
191  *
192  * Site specifications come in several forms:
193  * - /path/to/drupal#sitename
194  * - user@server/path/to/drupal#sitename
195  * - user@server/path/to/drupal            (sitename == server)
196  * - user@server#sitename                  (only if $option['r'] set in some drushrc file on server)
197  * - #sitename                             (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
198  * - sitename                              (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
199  *
200  * Note that in the case of the first four forms, it is also possible
201  * to add additional site variable to the specification using uri query
202  * syntax.  For example:
203  *
204  *      user@server/path/to/drupal?db-url=...#sitename
205  *
206  * @param alias
207  *   An alias name or site specification
208  * @return array
209  *   An alias record, or empty if none found.
210  */
211 function drush_sitealias_get_record($alias, $alias_context = NULL) {
212   // Check to see if the alias contains commas.  If it does, then
213   // we will go ahead and make a site list record
214   $alias_record = array();
215   if (strpos($alias, ',') !== false) {
216     // TODO:  If the site list contains any site lists, or site
217     // search paths, then we should expand those and merge them
218     // into this list longhand.
219     $alias_record['site-list'] = explode(',', $alias);
220   }
221   else {
222     $alias_record = _drush_sitealias_get_record($alias, $alias_context);
223   }
224   if (!empty($alias_record)) {
225     if (array_key_exists('#name', $alias_record)) {
226       if ($alias_record['#name'] == 'self') {
227         $path = drush_sitealias_local_site_path($alias_record);
228         if ($path) {
229           $cached_alias_record = drush_sitealias_lookup_alias_by_path($path);
230           // Don't overrite keys which have already been negotiated.
231           unset($cached_alias_record['#name'], $cached_alias_record['root'], $cached_alias_record['uri']);
232           $alias_record = array_merge($alias_record, $cached_alias_record);
233         }
234       }
235     }
236     else {
237       $alias_record['#name'] = drush_sitealias_uri_to_site_dir($alias);
238     }
239   }
240   return $alias_record;
241 }
242
243 /**
244  * This is a continuation of drush_sitealias_get_record, above.  It is
245  * not intended to be called directly.
246  */
247 function _drush_sitealias_get_record($alias, $alias_context = NULL) {
248   $alias_record = array();
249   // Before we do anything else, load $alias if it needs to be loaded
250   _drush_sitealias_load_alias($alias, $alias_context);
251
252   // Check to see if the provided parameter is in fact a defined alias.
253   $all_site_aliases =& drush_get_context('site-aliases');
254   if (array_key_exists($alias, $all_site_aliases)) {
255     $alias_record = $all_site_aliases[$alias];
256   }
257   // If the parameter is not an alias, then it is some form of
258   // site specification (or it is nothing at all)
259   else {
260     if (isset($alias)) {
261       // Cases 1.) - 4.):
262       // We will check for a site specification if the alias has at least
263       // two characters from the set '@', '/', '#'.
264       if ((strpos($alias, '@') === FALSE ? 0 : 1) + ((strpos($alias, '/') === FALSE && strpos($alias, '\\') === FALSE) ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) {
265         if ((substr($alias,0,7) != 'http://') && !drush_is_absolute_path($alias)) {
266           // Add on a scheme so that "user:pass@server" will always parse correctly
267           $parsed = parse_url('http://' . $alias);
268         }
269         else if (drush_is_windows() && drush_is_absolute_path($alias)) {
270           // On windows if alias begins with a filesystem path we must add file:// scheme to make it parse correcly
271           $parsed = parse_url('file:///' . $alias);
272         }
273         else {
274           $parsed = parse_url($alias);
275         }
276         // Copy various parts of the parsed URL into the appropriate records of the alias record
277         foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => 'root') as $url_key => $option_key) {
278           if (array_key_exists($url_key, $parsed)) {
279             _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]);
280           }
281         }
282         // If the site specification has a query, also set the query items
283         // in the alias record.  This allows passing db_url as part of the
284         // site specification, for example.
285         if (array_key_exists('query', $parsed)) {
286           foreach (explode('&', $parsed['query']) as $query_arg) {
287             $query_components = explode('=', $query_arg);
288             _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1]));
289           }
290         }
291
292         // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host
293         // Note: We presume that 'server' is the best default for case 3; without this code, the default would
294         // be whatever is set in $options['l'] on the target machine's drushrc.php settings file.
295         if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) {
296           $alias_record['uri'] = $parsed['host'];
297         }
298
299         // Special checking:  relative aliases embedded in a path
300         $relative_alias_pos = strpos($alias_record['root'], '/@');
301         if ($relative_alias_pos !== FALSE) {
302           // Special checking: /path/@sites
303           $base = substr($alias_record['root'], 0, $relative_alias_pos);
304           $relative_alias = substr($alias_record['root'], $relative_alias_pos + 1);
305           if (drush_valid_root($base) || ($relative_alias == '@sites')) {
306             drush_sitealias_create_sites_alias($base);
307             $alias_record = drush_sitealias_get_record($relative_alias);
308           }
309           else {
310             $alias_record = array();
311           }
312         }
313       }
314       else {
315         // Case 5.) and 6.):
316         // If the alias is the name of a folder in the 'sites' directory,
317         // then use it as a local site specification.
318         $alias_record = _drush_sitealias_find_record_for_local_site($alias);
319       }
320     }
321   }
322
323   if (!empty($alias_record)) {
324     if (!isset($alias_record['remote']) && !isset($alias_record['#loaded-config'])) {
325       if (array_key_exists('root', $alias_record)) {
326         drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush');
327         drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush');
328       }
329       // TODO: We should probably remove this feature, and put it back
330       // in, but in different places (e.g. site selection, sql-sync + rsync
331       // parameters, etc.)
332       $alias_site_dir = drush_sitealias_local_site_path($alias_record);
333
334       if (isset($alias_site_dir)) {
335         // Add the sites folder of this site to the alias search path list
336         drush_sitealias_add_to_alias_path($alias_site_dir);
337       }
338       if (isset($alias_record['config']) && file_exists($alias_record['config'])) {
339         drush_load_config_file('site', $alias_record['config']);
340         $alias_record['#loaded-config'] = TRUE;
341       }
342       unset($alias_record['config']);
343     }
344
345     // Add the static defaults
346     _drush_sitealias_add_static_defaults($alias_record);
347
348     // Cache the result with all of its calculated values
349     $all_site_aliases[$alias] = $alias_record;
350   }
351
352   return $alias_record;
353 }
354
355 /**
356  * Add a path to the array of paths where alias files are searched for.
357  *
358  * @param $add_path
359  *   A path to add to the search path (or NULL to not add any).
360  *   Once added, the new path will remain available until drush
361  *   exits.
362  * @return
363  *   An array of paths containing all values added so far
364  */
365 function drush_sitealias_add_to_alias_path($add_path) {
366   static $site_paths = array();
367
368   if ($add_path != NULL) {
369     if (!is_array($add_path)) {
370       $add_path = explode(PATH_SEPARATOR, $add_path);
371     }
372     // Normalize path to make sure we don't add the same path twice on
373     // windows due to different spelling. e.g. c:\tmp and c:/tmp
374     foreach($add_path as &$path) {
375       $path = drush_normalize_path($path);
376     }
377     $site_paths = array_unique(array_merge($site_paths, $add_path));
378   }
379   return $site_paths;
380 }
381
382 /**
383  * Return the array of paths where alias files are searched for.
384  *
385  * @param $alias_path_context
386  *   If the alias being looked up is part of a relative alias,
387  *   the alias path context specifies the context of the primary
388  *   alias the new alias is rooted from.  Alias files stored in
389  *   the sites folder of this context, or inside the context itself
390  *   takes priority over any other search path that might define
391  *   a similarly-named alias.  In this way, multiple sites can define
392  *   a '@peer' alias.
393  * @return
394  *   An array of paths
395  */
396 function drush_sitealias_alias_path($alias_path_context = NULL) {
397   $context_path = array();
398   if (isset($alias_path_context)) {
399     $context_path = array(drush_sitealias_local_site_path($alias_path_context));
400   }
401   // We get the current list of site paths by adding NULL
402   // (nothing) to the path list, which is a no-op
403   $site_paths = drush_sitealias_add_to_alias_path(NULL);
404
405   // If the user defined the root of a drupal site, then also
406   // look for alias files in /drush and /sites/all/drush.
407   $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
408   if (!empty($drupal_root)) {
409     $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/../drush');
410     $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/drush');
411     $site_paths[] = drush_sitealias_alias_base_directory($drupal_root . '/sites/all/drush');
412     $uri = drush_get_context('DRUSH_SELECTED_URI');
413     if (empty($uri)) {
414       $uri = 'default';
415     }
416     $site_dir = drush_sitealias_uri_to_site_dir($uri, $drupal_root);
417     if ($site_dir) {
418       $site_paths[] = drush_sitealias_alias_base_directory("$drupal_root/sites/$site_dir");
419     }
420   }
421   $alias_path = (array) drush_get_context('ALIAS_PATH', array());
422   return array_unique(array_merge($context_path, $alias_path, $site_paths));
423 }
424
425 /**
426  * If there is a directory 'site-aliases' in the specified search location,
427  * then search ONLY in that directory for aliases.  Otherwise, search
428  * anywhere inside the specified directory for aliases.
429  */
430 function drush_sitealias_alias_base_directory($dir) {
431   $potential_location = $dir . '/site-aliases';
432   if (is_dir($potential_location)) {
433     return $potential_location;
434   }
435   return $dir;
436 }
437
438 /**
439  * Return the full path to the site directory of the
440  * given alias record.
441  *
442  * @param $alias_record
443  *   The alias record
444  * @return
445  *   The path to the site directory of the associated
446  *   alias record, or NULL if the record is not a local site.
447  */
448 function drush_sitealias_local_site_path($alias_record) {
449   $result = NULL;
450
451   if (isset($alias_record['root']) && !isset($alias_record['remote-host'])) {
452     if (isset($alias_record['uri'])) {
453       $uri = $alias_record['uri'];
454       $uri = preg_replace('#^[^:]*://#', '', $uri);
455       while (!$result && !empty($uri)) {
456         if (file_exists($alias_record['root'] . '/sites/sites.php')) {
457           $sites = array();
458           include($alias_record['root'] . '/sites/sites.php');
459           if (array_key_exists($uri, $sites)) {
460             $result = $alias_record['root'] . '/sites/' . $sites[$uri];
461           }
462         }
463         if (!$result) {
464           $result = ($alias_record['root'] . '/sites/' . drush_sitealias_uri_to_site_dir($uri, drush_sitealias_get_root($alias_record)));
465         }
466         $result = realpath($result);
467         $uri = preg_replace('#^[^.]*\.*#', '', $uri);
468       }
469     }
470     if (!$result) {
471       $result = realpath($alias_record['root'] . '/sites/default');
472     }
473   }
474
475   return $result;
476 }
477
478 /**
479  * Check and see if an alias definition for $alias is available.
480  * If it is, load it into the list of aliases cached in the
481  * 'site-aliases' context.
482  *
483  * @param $alias
484  *   The name of the alias to load in ordinary form ('@name')
485  * @param $alias_path_context
486  *   When looking up a relative alias, the alias path context is
487  *   the primary alias that we will start our search from.
488  */
489 function _drush_sitealias_load_alias($alias, $alias_path_context = NULL) {
490   $all_site_aliases = drush_get_context('site-aliases');
491   $result = array();
492
493   // Only aliases--those named entities that begin with '@'--can be loaded this way.
494   // We also skip any alias that has already been loaded.
495   if ((substr($alias,0,1) == '@') && !array_key_exists($alias,$all_site_aliases)) {
496     $aliasname = substr($alias,1);
497     $result = _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context);
498     if (!empty($result)) {
499       $alias_options = array('site-aliases' => array($aliasname => $result));
500       _drush_sitealias_add_inherited_values($alias_options['site-aliases']);
501       drush_set_config_special_contexts($alias_options);
502       if (array_key_exists('#file', $result)) {
503         drush_log(dt('Loaded alias !alias from file !file', array('!alias' => $alias, '!file' => $result['#file'])));
504       }
505     }
506   }
507
508   return $result;
509 }
510
511 /**
512  * Load every alias file that can be found anywhere in the
513  * alias search path.
514  */
515 function drush_sitealias_load_all($resolve_parent = TRUE) {
516   $result = _drush_sitealias_find_and_load_all_aliases();
517   if (!empty($result) && ($resolve_parent == TRUE)) {
518     // If any aliases were returned, then check for
519     // inheritance and then store the aliases into the
520     // alias cache
521     _drush_sitealias_add_inherited_values($result);
522     $alias_options = array('site-aliases' => $result);
523     drush_set_config_special_contexts($alias_options);
524   }
525 }
526
527 /**
528  * Worker function called by _drush_sitealias_load_alias and
529  * drush_sitealias_load_all.  Traverses the alias search path
530  * and finds the specified alias record.
531  *
532  * @return
533  *   An array of $kay => $value pair of alias names and alias records
534  *   loaded.
535  */
536 function _drush_sitealias_find_and_load_all_aliases() {
537   $result = array();
538
539   $drush_alias_files = _drush_sitealias_find_alias_files();
540   drush_set_context('drush-alias-files', $drush_alias_files);
541
542   // For every file that matches, check inside it for
543   // an alias with a matching name.
544   foreach ($drush_alias_files as $filename) {
545     if (file_exists($filename)) {
546       $aliases = $options = array();
547       // silently ignore files we can't include
548       if ((@include $filename) === FALSE) {
549         drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
550         continue;
551       }
552       unset($options['site-aliases']); // maybe unnecessary
553
554       // If $aliases are not set, but $options are, then define one alias named
555       // after the first word of the file, before '.alias.drushrc.php.
556       if (empty($aliases) && !empty($options)) {
557         $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
558         $aliases[$this_alias_name] = $options;
559         $options = array();
560       }
561       // If this is a group alias file, then make an
562       // implicit alias from the group name that contains
563       // a site-list of all of the aliases in the file
564       $group_name = '';
565       if (substr($filename, -20) == ".aliases.drushrc.php") {
566         $group_name = basename($filename,".aliases.drushrc.php");
567         if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
568           $alias_names = array();
569           foreach (array_keys($aliases) as $one_alias) {
570             $alias_names[] = "@$group_name.$one_alias";
571             $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
572             unset($aliases[$one_alias]);
573           }
574           $aliases[$group_name] = array('site-list' => implode(',', $alias_names));
575         }
576       }
577       if (!empty($aliases)) {
578         if (!empty($options)) {
579           foreach ($aliases as $name => $value) {
580             $aliases[$name] = array_merge($options, $value);
581           }
582           $options = array();
583         }
584
585         foreach ($aliases as $name => $value) {
586           _drush_sitealias_initialize_alias_record($aliases[$name]);
587           $aliases[$name]['#name'] = $name;
588           $aliases[$name]['#file'] = $filename;
589         }
590
591         $result = _sitealias_array_merge($result, $aliases);
592         // If we found at least one alias from this file
593         // then record it in the drush-alias-files context.
594         $drush_alias_files = drush_get_context('drush-alias-files');
595         if (!in_array($filename, $drush_alias_files)) {
596           $drush_alias_files[] = $filename;
597         }
598         drush_set_context('drush-alias-files', $drush_alias_files);
599       }
600     }
601   }
602
603   return $result;
604 }
605
606 /**
607  * Function to find all alias files that might contain aliases
608  * that match the requested alias name.
609  */
610 function _drush_sitealias_find_alias_files($aliasname = NULL, $alias_path_context = NULL) {
611   $alias_files_to_consider = array();
612
613   // The alias path is a list of folders to search for alias settings files
614   $alias_path = drush_sitealias_alias_path($alias_path_context);
615
616   // $alias_files contains a list of filename patterns
617   // to search for.  We will find any matching file in
618   // any folder in the alias path.  The directory scan
619   // is not deep, though; only files immediately in the
620   // search path are considered.
621   $alias_files = array('/.*aliases\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/');
622   if ($aliasname == NULL) {
623     $alias_files[] = '/.*\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/';
624   }
625   else {
626     $alias_files[] = '/' . preg_quote($aliasname, '/') . '\.alias\.drush(' . DRUSH_MAJOR_VERSION . '|)rc\.php$/';
627   }
628
629   // Do not scan into the files directory.
630   $blacklist = array_merge(array('files'), drush_filename_blacklist());
631
632   // Search each path in turn.
633   foreach ($alias_path as $path) {
634     // Find all of the matching files in this location
635     foreach ($alias_files as $file_pattern_to_search_for) {
636       drush_log(dt('Scanning into @path for @pattern', array('@path' => $path, '@pattern' => $file_pattern_to_search_for)), LogLevel::DEBUG_NOTIFY);
637       $alias_files_to_consider = array_merge($alias_files_to_consider, array_keys(drush_scan_directory($path, $file_pattern_to_search_for, $blacklist, 0, TRUE)));
638     }
639   }
640
641   return $alias_files_to_consider;
642 }
643
644 /**
645  * Traverses the alias search path and finds the specified alias record.
646  *
647  * @param $aliasname
648  *   The name of the alias without the leading '@' (i.e. '#name')
649  *   or NULL to load every alias found in every alias file.
650  * @param $alias_path_context
651  *   When looking up a relative alias, the alias path context is
652  *   the primary alias that we will start our search from.
653  * @return
654  *   An empty array if nothing was loaded.  If $aliasname is
655  *   not null, then the array returned is the alias record for
656  *   $aliasname.  If $aliasname is NULL, then the array returned
657  *   is a $kay => $value pair of alias names and alias records
658  *   loaded.
659  */
660 function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = NULL) {
661   // Special checking for '@sites' alias
662   if ($aliasname == 'sites') {
663     $drupal_root = NULL;
664     if ($alias_path_context != null) {
665       if (array_key_exists('root', $alias_path_context) && !array_key_exists('remote-host', $alias_path_context)) {
666         $drupal_root = $alias_path_context['root'];
667       }
668     }
669     else {
670       $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
671     }
672     if (isset($drupal_root) && !is_array($drupal_root)) {
673       drush_sitealias_create_sites_alias($drupal_root);
674     }
675   }
676
677   $alias_files_to_consider = _drush_sitealias_find_alias_files($aliasname, $alias_path_context);
678
679   return _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider);
680 }
681
682 function _drush_sitealias_find_and_load_alias_from_file($aliasname, $alias_files_to_consider) {
683   $result = array();
684   $result_names = array();
685
686   // For every file that matches, check inside it for
687   // an alias with a matching name.
688   $recorded_files = array();
689   foreach ($alias_files_to_consider as $filename) {
690     if (file_exists($filename)) {
691       $aliases = $options = array();
692       // silently ignore files we can't include
693       if ((@include $filename) === FALSE) {
694         drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), LogLevel::BOOTSTRAP);
695         continue;
696       }
697       unset($options['site-aliases']); // maybe unnecessary
698
699       // If $aliases are not set, but $options are, then define one alias named
700       // after the first word of the file, before '.alias.drushrc.php.
701       if (empty($aliases) && !empty($options)) {
702         $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
703         $aliases[$this_alias_name] = $options;
704         $options = array();
705       }
706       // If this is a group alias file, then make an
707       // implicit alias from the group name that contains
708       // a site-list of all of the aliases in the file
709       $group_prefix = '';
710       if (substr($filename, -20) == ".aliases.drushrc.php") {
711         $group_name = basename($filename,".aliases.drushrc.php");
712         $group_prefix = $group_name . '.';
713         if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
714           $alias_names = array();
715           foreach (array_keys($aliases) as $one_alias) {
716             $alias_names[] = "@$group_name.$one_alias";
717             $aliases[$one_alias]['#name'] = "$group_name.$one_alias";
718             $aliases[$one_alias]['#group'] = $group_name;
719             $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
720             $aliases[$one_alias]["#hidden"] = TRUE;
721           }
722           $aliases[$group_name] = array('site-list' => implode(',', $alias_names), '#group' => $group_name, '#name' => $group_name);
723         }
724       }
725       // Store only the named alias into the alias cache
726       if ((isset($aliases)) && !empty($aliasname) && array_key_exists($aliasname, $aliases)) {
727         drush_set_config_special_contexts($options); // maybe unnecessary
728         $one_result = array_merge($options, $aliases[$aliasname]);
729         $one_result['#file'] = $filename;
730         if (!array_key_exists('#name', $one_result)) {
731           $one_result['#name'] = $aliasname;
732         }
733         _drush_sitealias_initialize_alias_record($one_result);
734         // If the alias name is exactly the same as a previous match, then
735         // merge the two records together
736         if (!empty($result) && ($result['#name'] == $one_result['#name'])) {
737           $result = _sitealias_array_merge($result, $one_result);
738         }
739         // Add the name of the found record to the list of results
740         else {
741           $result_names[] = "@" . $one_result['#name'];
742           $result = $one_result;
743         }
744       }
745     }
746   }
747   // If there are multiple matches, then return a list of results.
748   if (count($result_names) > 1) {
749     $result = array('site-list' => $result_names);
750   }
751
752   return $result;
753 }
754
755 /**
756  * Merges two site aliases.
757  *
758  * array_merge_recursive is too much; we only want to run
759  * array_merge on the common top-level keys of the array.
760  *
761  * @param array $site_alias_a
762  *   A site alias array.
763  * @param array $site_alias_b
764  *   A site alias array.
765  * @return
766  *   A site alias array where the keys from $site_alias_a are overwritten by the
767  *   keys from $site_alias_b.
768  */
769 function _sitealias_array_merge($site_alias_a, $site_alias_b) {
770   $result = $site_alias_a;
771
772   foreach($site_alias_b as $key => $value) {
773     if (is_array($value) && array_key_exists($key, $result)) {
774       $result[$key] = array_merge($result[$key], $value);
775     }
776     else {
777       $result[$key] = $value;
778     }
779   }
780
781   return $result;
782 }
783
784 /**
785  * Check to see if there is a 'parent' item in the alias; if there is,
786  * then load the parent alias record and overlay the entries in the
787  * current alias record on top of the items from the parent record.
788  *
789  * @param $aliases
790  *   An array of alias records that are modified in-place.
791  */
792 function _drush_sitealias_add_inherited_values(&$aliases) {
793   foreach ($aliases as $alias_name => $alias_value) {
794     // Prevent circular references from causing an infinite loop
795     _drush_sitealias_cache_alias("@$alias_name", array());
796     _drush_sitealias_add_inherited_values_to_record($alias_value);
797     $aliases[$alias_name] = $alias_value;
798   }
799 }
800
801 function _drush_sitealias_add_inherited_values_to_record(&$alias_value) {
802   drush_command_invoke_all_ref('drush_sitealias_alter', $alias_value);
803   if (isset($alias_value['parent'])) {
804     drush_log(dt("Using deprecated 'parent' element '!parent' in '!name'.", array('!parent' => $alias_value['parent'], '!name' => $alias_value['#name'])), LogLevel::DEBUG);
805     // Fetch and merge in each parent
806     foreach (explode(',', $alias_value['parent']) as $parent) {
807       $parent_record = drush_sitealias_get_record($parent);
808       unset($parent_record['#name']);
809       unset($parent_record['#file']);
810       unset($parent_record['#hidden']);
811       $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases'));
812       foreach ($array_based_keys as $array_based_key) {
813         if (isset($alias_value[$array_based_key]) && isset($parent_record[$array_based_key])) {
814           $alias_value[$array_based_key] = array_merge($parent_record[$array_based_key], $alias_value[$array_based_key]);
815         }
816       }
817       $alias_value = array_merge($parent_record, $alias_value);
818     }
819   }
820   unset($alias_value['parent']);
821 }
822
823 /**
824  * Add an empty record for the specified alias name
825  *
826  * @param $alias_name
827  *   The name of the alias, including the leading "@"
828  */
829 function _drush_sitealias_cache_alias($alias_name, $alias_record) {
830   $cache =& drush_get_context('site-aliases');
831   // If the alias already exists in the cache, then merge
832   // the new alias with the existing alias
833   if (array_key_exists($alias_name, $cache)) {
834     $alias_record = array_merge($cache[$alias_name], $alias_record);
835   }
836   if (!isset($alias_record['#name'])) {
837     $alias_record['#name'] = trim($alias_name, '@');
838   }
839   $cache[$alias_name] = $alias_record;
840
841   // If the alias record points at a local site, make sure
842   // that /drush, /sites/all/drush and the site folder for that site
843   // are added to the alias path, so that other alias files
844   // stored in those locations become searchable.
845   if (!array_key_exists('remote-host', $alias_record) && !empty($alias_record['root'])) {
846     drush_sitealias_add_to_alias_path($alias_record['root'] . '/drush');
847     drush_sitealias_add_to_alias_path($alias_record['root'] . '/sites/all/drush');
848     $site_dir = drush_sitealias_local_site_path($alias_record);
849     if (isset($site_dir)) {
850       drush_sitealias_add_to_alias_path($site_dir);
851     }
852   }
853 }
854
855 /**
856  * If the alias record does not contain a 'databases' or 'db-url'
857  * entry, then use backend invoke to look up the settings value
858  * from the remote or local site.  The 'db_url' form is preferred;
859  * nothing is done if 'db_url' is not available (e.g. on a D7 site)
860  *
861  * @param $alias_record
862  *   The full alias record to populate with database settings
863  */
864 function drush_sitealias_add_db_url(&$alias_record) {
865   if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
866     drush_sitealias_add_db_settings($alias_record);
867   }
868   if (!isset($alias_record['db-url']) && isset($alias_record['databases'])) {
869     $alias_record['db-url'] = drush_sitealias_convert_databases_to_db_url($alias_record['databases']);
870   }
871 }
872
873 /**
874  * Drush still accepts --db-url format database specifications as
875  * cli parameters; it is therefore useful to be able to convert
876  * from a database record back to a db-url sometimes.
877  */
878 function drush_sitealias_convert_db_spec_to_db_url($db_spec) {
879   $result = urlencode($db_spec["driver"]) . "://";
880   if (isset($db_spec["username"])) {
881     $result .= urlencode($db_spec["username"]);
882     if (isset($db_spec["password"])) {
883       $result .= ":" . urlencode($db_spec["password"]);
884     }
885     $result .= "@";
886   }
887   // Host is required, unless this is an sqlite db.
888   if (isset($db_spec["host"])) {
889     $result .= urlencode($db_spec["host"]);
890     if (isset($db_spec["port"])) {
891       $result .= ":" . urlencode($db_spec["port"]);
892     }
893     $result .= '/' . urlencode($db_spec["database"]);
894   }
895   else {
896     // URL-encode the database, but convert slashes
897     // back to their original form for readability.
898     // This portion is the "path" of the URL, so it may
899     // contain slashes.  This is important for sqlite.
900     $result .= str_replace("%2F", "/", urlencode(ltrim($db_spec["database"], '/')));
901   }
902   return $result;
903 }
904
905 /**
906  * Create a db-url from the databases record.
907  */
908 function drush_sitealias_convert_databases_to_db_url($databases) {
909   if ((count($databases) == 1) && isset($databases['default'])) {
910     $result = drush_sitealias_convert_db_spec_to_db_url($databases['default']['default']);
911   }
912   else {
913     foreach ($databases as $key => $db_info) {
914       $result[$key] = drush_sitealias_convert_db_spec_to_db_url($db_info['default']);
915     }
916   }
917   return $result;
918 }
919
920 /**
921  * Return the databases record from the alias record
922  *
923  * @param $alias_record
924  *   A record returned from drush_sitealias_get_record
925  * @returns
926  *   A databases record (always in D7 format) or NULL
927  *   if the databases record could not be found.
928  */
929 function sitealias_get_databases_from_record(&$alias_record) {
930   $altered_record = drush_sitealias_add_db_settings($alias_record);
931
932   return array_key_exists('databases', $alias_record) ? $alias_record['databases'] : NULL;
933 }
934
935 /**
936  * Return the $db_spec record for the database associated with
937  * the provided alias record.  @see drush_sitealias_add_db_settings(),
938  * which will be used to first add the database information to the
939  * alias records, invoking sql-conf to look them up if necessary.
940  *
941  * The options 'database' and 'target' are used to specify which
942  * specific database should be fetched from the database record;
943  * they may appear in the alias definition, or may be taken from the
944  * command line options.  The values 'default' and 'default' are
945  * used if these options are not specified in either location.
946  *
947  * Note that in the context of sql-sync, the site alias record will
948  * be taken from one of the source or target aliases
949  * (e.g. `drush sql-sync @source @target`), which will be overlayed with
950  * any options that begin with 'source-' or 'target-', respectively.
951  * Therefore, the commandline options 'source-database' and 'source-target'
952  * (or 'target-database' and 'source-target') may also affect the operation
953  * of this function.
954  */
955 function drush_sitealias_get_db_spec(&$alias_record, $default_to_self = FALSE, $prefix = '') {
956   $db_spec = NULL;
957   $databases = sitealias_get_databases_from_record($alias_record);
958   if (isset($databases) && !empty($databases)) {
959     $database = drush_sitealias_get_option($alias_record, 'database', 'default', $prefix);
960     $target = drush_sitealias_get_option($alias_record, 'target', 'default', $prefix);
961     if (array_key_exists($database, $databases) && array_key_exists($target, $databases[$database])) {
962       $db_spec = $databases[$database][$target];
963     }
964   }
965   elseif ($default_to_self) {
966     $db_spec = _drush_sql_get_db_spec();
967   }
968
969   if (isset($db_spec)) {
970     $remote_host = drush_sitealias_get_option($alias_record, 'remote-host', NULL, $prefix);
971     if (!drush_is_local_host($remote_host)) {
972       $db_spec['remote-host'] = $remote_host;
973       $db_spec['port'] = drush_sitealias_get_option($alias_record, 'remote-port', (isset($db_spec['port']) ? $db_spec['port'] : NULL), $prefix);
974     }
975   }
976
977   return $db_spec;
978 }
979
980 /**
981  * If the alias record does not contain a 'databases' or 'db-url'
982  * entry, then use backend invoke to look up the settings value
983  * from the remote or local site.  The 'databases' form is
984  * preferred; 'db_url' will be converted to 'databases' if necessary.
985  *
986  * @param $alias_record
987  *   The full alias record to populate with database settings
988  */
989 function drush_sitealias_add_db_settings(&$alias_record) {
990   $altered_record = FALSE;
991   if (isset($alias_record['root'])) {
992     // If the alias record does not have a defined 'databases' entry,
993     // then we'll need to look one up
994     if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
995       $values = drush_invoke_process($alias_record, "sql-conf", array(), array('all' => TRUE), array('integrate' => FALSE, 'override-simulated' => TRUE));
996       if (is_array($values) && ($values['error_status'] == 0)) {
997         $altered_record = TRUE;
998         // If there are any special settings in the '@self' record returned by drush_invoke_process,
999         // then add those into our altered record as well
1000         if (array_key_exists('self', $values)) {
1001           $alias_record = array_merge($values['self'], $alias_record);
1002         }
1003         drush_sitealias_cache_db_settings($alias_record, $values['object']);
1004       }
1005     }
1006   }
1007   return $altered_record;
1008 }
1009
1010 function drush_sitealias_cache_db_settings(&$alias_record, $databases) {
1011   if (!empty($databases)) {
1012     $alias_record['databases'] = $databases;
1013   }
1014
1015   // If the name is set, then re-cache the record after we fetch the databases
1016   if (array_key_exists('#name', $alias_record)) {
1017     $all_site_aliases =& drush_get_context('site-aliases');
1018     $all_site_aliases['@' . $alias_record['#name']] = $alias_record;
1019     // Check and see if this record is a copy of 'self'
1020     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'])) {
1021       $all_site_aliases['@self'] = $alias_record;
1022     }
1023   }
1024 }
1025
1026 /**
1027  * Check to see if we have already bootstrapped to a site.
1028  */
1029 function drush_sitealias_is_bootstrapped_site($alias_record) {
1030   if (!isset($alias_record['remote-host']) && array_key_exists('root', $alias_record)) {
1031     $self_record = drush_sitealias_get_record("@self");
1032     if (empty($self_record) || !array_key_exists('root', $self_record)) {
1033       // TODO:  If we have not bootstrapped to a site yet, we could
1034       // perhaps bootstrap to $alias_record here.
1035       return FALSE;
1036     }
1037     elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) {
1038       return TRUE;
1039     }
1040   }
1041   return FALSE;
1042 }
1043
1044 /**
1045  * Determines whether a given site alias is for a remote site.
1046  *
1047  * @param string $alias
1048  *   An alias name or site specification.
1049  *
1050  * @return bool
1051  *   Returns TRUE if the alias refers to a remote site, FALSE if it does not, or NULL is unsure.
1052  */
1053 function drush_sitealias_is_remote_site($alias) {
1054   if (is_array($alias) && !empty($alias['remote-host'])) {
1055     return TRUE;
1056   }
1057   if (!is_string($alias) || !strlen($alias)) {
1058     return NULL;
1059   }
1060
1061   $site_record = drush_sitealias_get_record($alias);
1062   if ($site_record) {
1063     if (!empty($site_record['remote-host'])) {
1064       return TRUE;
1065     }
1066     else {
1067       return FALSE;
1068     }
1069   }
1070   else {
1071     drush_set_error('Unrecognized site alias.');
1072   }
1073 }
1074
1075 /**
1076  * Get the name of the current bootstrapped site
1077  */
1078 function drush_sitealias_bootstrapped_site_name() {
1079   $site_name = NULL;
1080   $self_record = drush_sitealias_get_record('@self');
1081   if (array_key_exists('#name', $self_record)) {
1082     $site_name = $self_record['#name'];
1083   }
1084   if (!isset($site_name) || ($site_name == '@self')) {
1085     $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
1086     if (isset($drupal_root)) {
1087       $drupal_uri = drush_get_context('DRUSH_SELECTED_URI', 'default');
1088       $drupal_uri = str_replace('http://', '', $drupal_uri);
1089       // TODO: Maybe use _drush_sitealias_find_local_alias_name?
1090       $site_name = $drupal_root . '#' . $drupal_uri;
1091     }
1092   }
1093   return $site_name;
1094 }
1095
1096 /**
1097  * If there are any path aliases (items beginning with "%") in the test
1098  * string, then resolve them as path aliases and add them to the provided
1099  * alias record.
1100  *
1101  * @param $alias_record
1102  *   The full alias record to use in path alias expansion
1103  * @param $test_string
1104  *   A slash-separated list of path aliases to resolve
1105  *   e.g. "%files/%special".
1106  */
1107 function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') {
1108   $path_aliases = array_key_exists('path-aliases', $alias_record) ? $alias_record['path-aliases'] : array();
1109   // Convert the test string into an array of items, and
1110   // from this make a comma-separated list of projects
1111   // that we can pass to 'drush status'.
1112   $test_array = explode('/', $test_string);
1113   $project_array = array();
1114   foreach($test_array as $one_item) {
1115     if (!empty($one_item) && ($one_item[0] == '%') && (!array_key_exists($one_item,$path_aliases))) {
1116       $project_array[] = substr($one_item,1);
1117     }
1118   }
1119   $project_list = implode(',', $project_array);
1120
1121   if (!empty($project_array)) {
1122     // Optimization:  if we're already bootstrapped to the
1123     // site specified by $alias_record, then we can just
1124     // call _core_site_status_table() rather than use backend invoke.
1125     if (drush_sitealias_is_bootstrapped_site($alias_record) && drush_has_boostrapped(DRUSH_BOOTSTRAP_DRUPAL_FULL)) {
1126       $status_values = _core_site_status_table($project_list);
1127     }
1128     else {
1129       $values = drush_invoke_process($alias_record, "core-status", array(), empty($project_list) ? array() : array('project' => $project_list), array('integrate' => FALSE, 'override-simulated' => TRUE));
1130       $status_values = $values['object'];
1131     }
1132     if (isset($status_values['%paths'])) {
1133       foreach ($status_values['%paths'] as $key => $path) {
1134         $alias_record['path-aliases'][$key] = $path;
1135       }
1136     }
1137     // If 'root' is not set in the alias, then fill it in from the status values.
1138     if (!isset($alias_record['root']) && isset($status_values['root'])) {
1139       $alias_record['root'] = $status_values['root'];
1140     }
1141   }
1142 }
1143
1144 /**
1145  * Given an alias record that is a site list (contains a 'site-list' entry),
1146  * resolve all of the members of the site list and return them
1147  * is an array of alias records.
1148  *
1149  * @param $alias_record
1150  *   The site list alias record array
1151  * @return
1152  *   An array of individual site alias records
1153  */
1154 function drush_sitealias_resolve_sitelist($alias_record) {
1155   $result_list = array();
1156   if (isset($alias_record)) {
1157     if (array_key_exists('site-list', $alias_record)) {
1158       foreach ($alias_record['site-list'] as $sitespec) {
1159         $one_result = drush_sitealias_get_record($sitespec);
1160         $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result));
1161       }
1162     }
1163     elseif (array_key_exists('#name', $alias_record)) {
1164       $result_list[$alias_record['#name']] = $alias_record;
1165     }
1166   }
1167
1168   return $result_list;
1169 }
1170
1171 function _drush_sitelist_find_in_list($one_source, &$target) {
1172   $result = FALSE;
1173
1174   foreach ($target as $key => $one_target) {
1175     if(_drush_sitelist_check_site_records($one_source, $one_target)) {
1176       $result = $one_target;
1177       unset($target[$key]);
1178     }
1179   }
1180
1181   return $result;
1182 }
1183
1184 function _drush_sitelist_check_site_records($source, $target) {
1185   if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) {
1186     return TRUE;
1187   }
1188   return FALSE;
1189 }
1190
1191 /**
1192  * Initialize an alias record; called as soon as the alias
1193  * record is loaded from its alias file, before it is stored
1194  * in the cache.
1195  *
1196  * @param alias_record
1197  *   The alias record to be initialized; parameter is modified in place.
1198  */
1199 function _drush_sitealias_initialize_alias_record(&$alias_record) {
1200   // If there is a 'from-list' entry, then build a derived
1201   // list based on the site list with the given name.
1202   if (array_key_exists('from-list', $alias_record)) {
1203     // danger of infinite loops... move to transient defaults?
1204     $from_record = drush_sitealias_get_record($alias_record['from-list']);
1205     $from_list = drush_sitealias_resolve_sitelist($from_record);
1206     $derived_list = array();
1207     foreach ($from_list as $one_record) {
1208       $derived_record = _drush_sitealias_derive_record($one_record, $alias_record);
1209       $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record);
1210     }
1211
1212     $alias_record = array();
1213     if (!empty($derived_list)) {
1214       $alias_record['site-list'] = $derived_list;
1215     }
1216   }
1217   // If there is a 'site-search-path' entry, then build
1218   // a 'site-list' entry from all of the sites that can be
1219   // found in the search path.
1220   if (array_key_exists('site-search-path', $alias_record)) {
1221     // TODO:  Is there any point in merging the sites from
1222     // the search path with any sites already listed in the
1223     // 'site-list' entry?  For now we'll just overwrite.
1224     $search_path = $alias_record['site-search-path'];
1225     if (!is_array($search_path)) {
1226       $search_path = explode(',', $search_path);
1227     }
1228     $found_sites = _drush_sitealias_find_local_sites($search_path);
1229     $alias_record['site-list'] = $found_sites;
1230     // The 'unordered-list' flag indicates that the order of the items in the site list is not stable.
1231     $alias_record['unordered-list'] = '1';
1232     // DEBUG: var_export($alias_record, FALSE);
1233   }
1234   if (array_key_exists('site-list', $alias_record)) {
1235     if (!is_array($alias_record['site-list'])) {
1236       $alias_record['site-list'] = explode(',', $alias_record['site-list']);
1237     }
1238   }
1239   else {
1240     if (isset($alias_record['root']) && !isset($alias_recort['uri'])) {
1241       $alias_recort['uri'] = 'default';
1242     }
1243   }
1244 }
1245
1246 /**
1247  * Add "static" default values to the given alias record.  The
1248  * difference between a static default and a transient default is
1249  * that static defaults -always- exist in the alias record, and
1250  * they are cached, whereas transient defaults are only added
1251  * if the given drush command explicitly adds them.
1252  *
1253  * @param alias_record
1254  *   An alias record with most values already filled in
1255  */
1256 function _drush_sitealias_add_static_defaults(&$alias_record) {
1257   // If there is a 'db-url' entry but not 'databases' entry, then we will
1258   // build 'databases' from 'db-url' so that drush commands that use aliases
1259   // can always count on using a uniform 'databases' array.
1260   if (isset($alias_record['db-url']) && !isset($alias_record['databases'])) {
1261     $alias_record['databases'] = drush_sitealias_convert_db_from_db_url($alias_record['db-url']);
1262   }
1263
1264   // Canonicalize paths.
1265   if (!empty($alias_record['root'])) {
1266     $alias_record['root'] = Path::canonicalize($alias_record['root']);
1267   }
1268
1269   // Adjustments for aliases to drupal instances (as opposed to aliases that are site lists)
1270   if (array_key_exists('uri', $alias_record)) {
1271     // Make sure that there is always a 'path-aliases' array
1272     if (!array_key_exists('path-aliases', $alias_record)) {
1273       $alias_record['path-aliases'] = array();
1274     }
1275     // If there is a 'root' entry, then copy it to the '%root' path alias
1276     if (isset($alias_record['root'])) {
1277       $alias_record['path-aliases']['%root'] = $alias_record['root'];
1278     }
1279   }
1280 }
1281
1282 function _drush_sitealias_derive_record($from_record, $modifying_record) {
1283   $result = $from_record;
1284
1285   // If there is a 'remote-user' in the modifying record, copy it.
1286   if (array_key_exists('remote-user', $modifying_record)) {
1287     $result['remote-user'] = $from_record['remote_user'];
1288   }
1289   // If there is a 'remote-host', then:
1290   //   If it is empty, clear the remote host in the result record
1291   //   If it ends in '.', then prepend it to the remote host in the result record
1292   //   Otherwise, copy it to the result record
1293   if (array_key_exists('remote-host', $modifying_record)) {
1294     $remote_host_modifier = $modifying_record['remote-host'];
1295     if(empty($remote_host_modifier)) {
1296       unset($result['remote-host']);
1297       unset($result['remote-user']);
1298     }
1299     elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') {
1300       $result['remote-host'] = $remote_host_modifier . $result['remote-host'];
1301     }
1302     else {
1303       $result['remote-host'] = $remote_host_modifier;
1304     }
1305   }
1306   // If there is a 'root', then:
1307   //   If it begins with '/', copy it to the result record
1308   //   Otherwise, append it to the result record
1309   if (array_key_exists('root', $modifying_record)) {
1310     $root_modifier = $modifying_record['root'];
1311     if($root_modifier[0] == '/') {
1312       $result['root'] = $root_modifier;
1313     }
1314     else {
1315       $result['root'] = $result['root'] . '/' . $root_modifier;
1316     }
1317   }
1318   // Poor man's realpath: take out the /../ with preg_replace.
1319   // (realpath fails if the files in the path do not exist)
1320   while(strpos($result['root'], '/../') !== FALSE) {
1321     $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']);
1322   }
1323
1324   // TODO:  Should we allow the uri to be transformed?
1325   // I think that if the uri does not match, then you should
1326   // always build the list by hand, and not rely on '_drush_sitealias_derive_record'.
1327
1328   return $result;
1329 }
1330
1331 /**
1332  * Convert from an alias record to a site specification
1333  *
1334  * @param alias_record
1335  *   The full alias record to convert
1336  *
1337  * @param with_db
1338  *   True if the site specification should include a ?db-url term
1339  *
1340  * @return string
1341  *   The site specification
1342  */
1343 function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) {
1344     $result = '';
1345
1346     // TODO:  we should handle 'site-list' records too.
1347     if (array_key_exists('site-list', $alias_record)) {
1348       // TODO:  we should actually expand the site list and recompose it
1349       $result = implode(',', $alias_record['site-list']);
1350     }
1351     else {
1352       // There should always be a uri
1353       if (array_key_exists('uri', $alias_record)) {
1354         $result = '#' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record));
1355       }
1356       // There should always be a root
1357       if (array_key_exists('root', $alias_record)) {
1358         $result = $alias_record['root'] . $result;
1359       }
1360       if (array_key_exists('remote-host', $alias_record)) {
1361         $result = drush_remote_host($alias_record) . $result;
1362       }
1363
1364       // Add the database info to the specification if desired
1365       if ($with_db) {
1366         // If db-url is not supplied, look it up from the remote
1367         // or local site and add it to the site alias
1368         if (!isset($alias_record['db-url'])) {
1369           drush_sitealias_add_db_url($alias_record);
1370         }
1371         $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']);
1372       }
1373     }
1374
1375     return $result;
1376 }
1377
1378 /**
1379  * Search for drupal installations in the search path.
1380  *
1381  * @param search_path
1382  *   An array of drupal root folders
1383  *
1384  * @return
1385  *   An array of site specifications (/path/to/root#sitename.com)
1386  */
1387 function _drush_sitealias_find_local_sites($search_path) {
1388   $result = array();
1389   foreach ($search_path as $a_drupal_root) {
1390     $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root));
1391   }
1392   return $result;
1393 }
1394
1395 /**
1396  * Return a list of all of the local sites at the specified drupal root.
1397  */
1398 function _drush_find_local_sites_at_root($a_drupal_root = '', $search_depth = 1) {
1399   $site_list = array();
1400   $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root );
1401   if (!empty($base_path)) {
1402     if (drush_valid_root($base_path)) {
1403       // If $a_drupal_root is in fact a valid drupal root, then return
1404       // all of the sites found inside the 'sites' folder of this drupal instance.
1405       $site_list = _drush_find_local_sites_in_sites_folder($base_path);
1406     }
1407     else {
1408       $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);
1409       foreach ($bootstrap_files as $one_bootstrap => $info) {
1410         $includes_dir = dirname($one_bootstrap);
1411         if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_SIGNATURE))) {
1412           $drupal_root = dirname($includes_dir);
1413           $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list);
1414         }
1415       }
1416     }
1417   }
1418   return $site_list;
1419 }
1420
1421 /**
1422  * Return a list of all of the local sites at the specified 'sites' folder.
1423  */
1424 function _drush_find_local_sites_in_sites_folder($a_drupal_root) {
1425   $site_list = array();
1426
1427   // If anyone searches for sites at a given root, then
1428   // make sure that alias files stored at this root
1429   // directory are included in the alias search path
1430   drush_sitealias_add_to_alias_path($a_drupal_root);
1431
1432   $base_path = $a_drupal_root . '/sites';
1433
1434   // TODO:  build a cache keyed off of $base_path (realpath($base_path)?),
1435   // so that it is guarenteed that the lists returned will definitely be
1436   // exactly the same should this routine be called twice with the same path.
1437
1438   $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1, 'filename', 1);
1439   foreach ($files as $filename => $info) {
1440     if ($info->basename == 'settings.php') {
1441       // First we'll resolve the realpath of the settings.php file,
1442       // so that we get the correct drupal root when symlinks are in use.
1443       $real_sitedir = dirname(realpath($filename));
1444       $real_root = drush_locate_root($filename);
1445       if ($real_root !== FALSE) {
1446         $a_drupal_site = $real_root . '#' . basename($real_sitedir);
1447       }
1448       // If the symlink points to some folder outside of any drupal
1449       // root, then we'll use the
1450       else {
1451         $uri = drush_sitealias_site_dir_from_filename($filename);
1452         $a_drupal_site = $a_drupal_root . '#' . $uri;
1453       }
1454       // Add the site if it isn't already in the array
1455       if (!in_array($a_drupal_site, $site_list)) {
1456         $site_list[] = $a_drupal_site;
1457       }
1458     }
1459   }
1460   return $site_list;
1461 }
1462
1463 function drush_sitealias_create_sites_alias($a_drupal_root = '') {
1464   $sites_list = _drush_find_local_sites_at_root($a_drupal_root);
1465   _drush_sitealias_cache_alias('@sites', array('site-list' => $sites_list));
1466 }
1467
1468 /**
1469  * Add "transient" default values to the given alias record.  The
1470  * difference between a static default and a transient default is
1471  * that static defaults -always- exist in the alias record,
1472  * whereas transient defaults are only added if the given drush
1473  * command explicitly calls this function.  The other advantage
1474  * of transient defaults is that it is possible to differentiate
1475  * between a default value and an unspecified value, since the
1476  * transient defaults are not added until requested.
1477  *
1478  * Since transient defaults are not cached, you should avoid doing
1479  * expensive operations here.  To be safe, drush commands should
1480  * avoid calling this function more than once.
1481  *
1482  * @param alias_record
1483  *   An alias record with most values already filled in
1484  */
1485 function _drush_sitealias_add_transient_defaults(&$alias_record) {
1486   if (isset($alias_record['path-aliases'])) {
1487     // Add the path to the drush folder to the path aliases as !drush
1488     if (!array_key_exists('%drush', $alias_record['path-aliases'])) {
1489       if (array_key_exists('%drush-script', $alias_record['path-aliases'])) {
1490         $alias_record['path-aliases']['%drush'] = dirname($alias_record['path-aliases']['%drush-script']);
1491       }
1492       else {
1493         $alias_record['path-aliases']['%drush'] = dirname(drush_find_drush());
1494       }
1495     }
1496     // Add the path to the site folder to the path aliases as !site
1497     if (!array_key_exists('%site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) {
1498       $alias_record['path-aliases']['%site'] = 'sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri'], drush_sitealias_get_root($alias_record)) . '/';
1499     }
1500   }
1501 }
1502
1503 /**
1504  * Find the name of a local alias record that has the specified
1505  * root and uri.
1506  */
1507 function _drush_sitealias_find_local_alias_name($root, $uri) {
1508   $result = '';
1509   $all_site_aliases =& drush_get_context('site-aliases');
1510
1511   foreach ($all_site_aliases as $alias_name => $alias_values) {
1512     if (!array_key_exists('remote-host', $alias_values) && array_key_exists('root', $alias_values) && array_key_exists('uri', $alias_values) && ($alias_name != '@self')) {
1513       if (($root == $alias_values['root']) && ($uri == $alias_values['uri'])) {
1514         $result = $alias_name;
1515       }
1516     }
1517   }
1518
1519   return $result;
1520 }
1521
1522 /**
1523  * If '$alias' is the name of a folder in the sites folder of the given drupal
1524  * root, then build an alias record for it
1525  *
1526  * @param alias
1527  *   The name of the site in the 'sites' folder to convert
1528  * @return array
1529  *   An alias record, or empty if none found.
1530  */
1531 function _drush_sitealias_find_record_for_local_site($alias, $drupal_root = NULL) {
1532   $alias_record = array();
1533
1534   // Clip off the leading '#' if it is there
1535   if (substr($alias,0,1) == '#') {
1536     $alias = substr($alias,1);
1537   }
1538
1539   if (!isset($drupal_root)) {
1540     $drupal_root = drush_get_context('DRUSH_SELECTED_DRUPAL_ROOT');
1541   }
1542
1543   if (!empty($drupal_root)) {
1544     $alias_dir = drush_sitealias_uri_to_site_dir($alias, $drupal_root);
1545     $site_settings_file = $drupal_root . '/sites/' . $alias_dir . '/settings.php';
1546     $alias_record = drush_sitealias_build_record_from_settings_file($site_settings_file, $alias, $drupal_root);
1547   }
1548
1549   return $alias_record;
1550 }
1551
1552 function drush_sitealias_build_record_from_settings_file($site_settings_file, $alias = null, $drupal_root = null) {
1553   $alias_record = array();
1554
1555   if (file_exists($site_settings_file)) {
1556     if (!isset($drupal_root)) {
1557       $drupal_root = drush_locate_root($site_settings_file);
1558     }
1559
1560     $alias_record['root'] = $drupal_root;
1561     if (isset($alias)) {
1562       $alias_record['uri'] = $alias;
1563     }
1564     else {
1565       $alias_record['uri'] = _drush_sitealias_site_dir_to_uri(drush_sitealias_site_dir_from_filename($site_settings_file));
1566     }
1567   }
1568
1569   return $alias_record;
1570 }
1571
1572 /**
1573  * Pull the site directory from the path to settings.php
1574  *
1575  * @param site_settings_file
1576  *   path to settings.php
1577  *
1578  * @return string
1579  *   the site directory component of the path to settings.php
1580  */
1581 function drush_sitealias_site_dir_from_filename($site_settings_file) {
1582   return basename(dirname($site_settings_file));
1583 }
1584
1585 /**
1586  * Convert from a URI to a site directory.
1587  *
1588  * @param uri
1589  *   A uri, such as http://domain.com:8080/drupal
1590  * @return string
1591  *   A directory, such as domain.com.8080.drupal
1592  */
1593 function drush_sitealias_uri_to_site_dir($uri, $site_root = NULL) {
1594   $uri = str_replace('http://', '', $uri);
1595   $uri = str_replace('https://', '', $uri);
1596   if (drush_is_windows()) {
1597     // Handle absolute paths on windows
1598     $uri = str_replace(array(':/', ':\\'), array('.', '.'), $uri);
1599   }
1600
1601   $hostname = str_replace(array('/', ':', '\\'), array('.', '.', '.'), $uri);
1602
1603   // Check sites.php mappings
1604   $site_dir = drush_site_dir_lookup_from_hostname($hostname, $site_root);
1605
1606   return $site_dir ? $site_dir : $hostname;
1607 }
1608
1609 /**
1610  * Convert from an old-style database URL to an array of database settings.
1611  *
1612  * @param db_url
1613  *   A Drupal 6 db url string to convert, or an array with a 'default' element.
1614  * @return array
1615  *   An array of database values containing only the 'default' element of
1616  *   the db url. If the parse fails the array is empty.
1617  */
1618 function drush_convert_db_from_db_url($db_url) {
1619   $db_spec = array();
1620
1621   if (is_array($db_url)) {
1622     $db_url_default = $db_url['default'];
1623   }
1624   else {
1625     $db_url_default = $db_url;
1626   }
1627
1628   // If it's a sqlite database, pick the database path and we're done.
1629   if (strpos($db_url_default, 'sqlite://') === 0) {
1630     $db_spec = array(
1631       'driver'   => 'sqlite',
1632       'database' => substr($db_url_default, strlen('sqlite://')),
1633     );
1634   }
1635   else {
1636     $url = parse_url($db_url_default);
1637     if ($url) {
1638       // Fill in defaults to prevent notices.
1639       $url += array(
1640         'scheme' => NULL,
1641         'user'   => NULL,
1642         'pass'   => NULL,
1643         'host'   => NULL,
1644         'port'   => NULL,
1645         'path'   => NULL,
1646       );
1647       $url = (object)array_map('urldecode', $url);
1648       $db_spec = array(
1649         'driver'   => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme,
1650         'username' => $url->user,
1651         'password' => $url->pass,
1652         'host' => $url->host,
1653         'port' => $url->port,
1654         'database' => ltrim($url->path, '/'),
1655       );
1656     }
1657   }
1658
1659   return $db_spec;
1660 }
1661
1662 /**
1663  * Convert from an old-style database URL to an array of database settings
1664  *
1665  * @param db_url
1666  *   A Drupal 6 db-url string to convert, or an array with multiple db-urls.
1667  * @return array
1668  *   An array of database values.
1669  */
1670 function drush_sitealias_convert_db_from_db_url($db_url) {
1671   $result = array();
1672
1673   if (!is_array($db_url)) {
1674     $result = array('default' => array('default' => drush_convert_db_from_db_url($db_url)));
1675   }
1676   else {
1677     foreach ($db_url as $one_name => $one_db_url) {
1678       $result[$one_name] = array('default' => drush_convert_db_from_db_url($one_db_url));
1679     }
1680   }
1681
1682   return $result;
1683 }
1684
1685 /**
1686  * Utility function used by drush_get_alias; keys that start with
1687  * '%' or '!' are path aliases, the rest are entries in the alias record.
1688  */
1689 function _drush_sitealias_set_record_element(&$alias_record, $key, $value) {
1690   if ((substr($key,0,1) == '%') || (substr($key,0,1) == '!')) {
1691     $alias_record['path-aliases'][$key] = $value;
1692   }
1693   elseif (!empty($key)) {
1694     $alias_record[$key] = $value;
1695   }
1696 }
1697
1698 /**
1699  * Looks up the specified alias record and calls through to
1700  * drush_sitealias_set_alias_context, below.
1701  *
1702  * @param alias
1703  *   The name of the alias record
1704  * @param prefix
1705  *   The prefix value to afix to the beginning of every
1706  *   key set.
1707  * @return boolean
1708  *   TRUE is an alias was found and processed.
1709  */
1710 function _drush_sitealias_set_context_by_name($alias, $prefix = '') {
1711   if ($alias) {
1712     $site_alias_settings = drush_sitealias_get_record($alias);
1713     if (!empty($site_alias_settings)) {
1714       drush_sitealias_set_alias_context($site_alias_settings, $prefix);
1715       drush_sitealias_cache_alias_by_path($site_alias_settings);
1716       if (empty($prefix)) {
1717
1718         // Create an alias '@self'
1719         // Allow 'uri' from the commandline to override
1720         $drush_uri = drush_get_option(array('uri', 'l'), FALSE);
1721         if ($drush_uri) {
1722           $site_alias_settings['uri'] = $drush_uri;
1723         }
1724
1725         _drush_sitealias_cache_alias('@self', $site_alias_settings);
1726         // Change the selected site to match the new --root and --uri, if any were set.
1727         _drush_preflight_root_uri();
1728       }
1729       return $site_alias_settings;
1730     }
1731   }
1732   return array();
1733 }
1734
1735 /**
1736  * Given an alias record, overwrite its values with options
1737  * from the command line and other drush contexts as specified
1738  * by the provided prefix.  For example, if the prefix is 'source-',
1739  * then any option 'source-foo' will set the value 'foo' in the
1740  * alias record.
1741  */
1742 function drush_sitealias_overlay_options($site_alias_record, $prefix) {
1743   return array_merge($site_alias_record, drush_get_merged_prefixed_options($prefix));
1744 }
1745
1746 /**
1747  * First return an option set via drush_sitealias_overlay_options, if
1748  * any, then fall back on "%" . $option from the path aliases.
1749  */
1750 function drush_sitealias_get_path_option($site_alias_record, $option, $default = NULL) {
1751   if (isset($site_alias_record) && array_key_exists($option, $site_alias_record)) {
1752     return $site_alias_record[$option];
1753   }
1754   if (isset($site_alias_record) && array_key_exists('path-aliases', $site_alias_record) && array_key_exists("%$option", $site_alias_record['path-aliases'])) {
1755     return $site_alias_record['path-aliases']["%$option"];
1756   }
1757   else {
1758     return drush_get_option($option, $default);
1759   }
1760 }
1761
1762 /**
1763  * Given a site alias record, copy selected fields from it
1764  * into the drush 'alias' context.  The 'alias' context has
1765  * lower precedence than the 'cli' context, so values
1766  * set by an alias record can be overridden by command-line
1767  * parameters.
1768  *
1769  * @param site_alias_settings
1770  *   An alias record
1771  * @param prefix
1772  *   The prefix value to affix to the beginning of every
1773  *   key set.  For example, if this function is called once with
1774  *   'source-' and again with 'destination-' prefixes, then the
1775  *   source database records will be stored in 'source-databases',
1776  *   and the destination database records will be in
1777  *   'destination-databases'.
1778  */
1779 function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') {
1780   $options = drush_get_context('alias');
1781
1782   // There are some items that we should just skip
1783   $skip_list = drush_get_special_keys();
1784   // If 'php-options' are set in the alias, then we will force drush
1785   // to redispatch via the remote dispatch mechanism even if the target is localhost.
1786   if ((array_key_exists('php-options', $site_alias_settings) || array_key_exists('php', $site_alias_settings)) && !drush_get_context('DRUSH_BACKEND', FALSE)) {
1787     if (!array_key_exists('remote-host', $site_alias_settings)) {
1788       $site_alias_settings['remote-host'] = 'localhost';
1789     }
1790   }
1791   // If 'php-options' are not set in the alias, then skip 'remote-host'
1792   // and 'remote-user' if 'remote-host' is actually the local machine.
1793   // This prevents drush from using the remote dispatch mechanism (the command
1794   // is just run directly on the local machine, bootstrapping to the specified alias)
1795   elseif (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) {
1796     $skip_list[] = 'remote-host';
1797     $skip_list[] = 'remote-user';
1798   }
1799   // If prefix is set, then copy from the 'prefix-' version
1800   // of the drush special keys ('command-specific', 'path-aliases')
1801   // into the ordinary version.  This will allow us to set
1802   // 'source-command-specific' options that will only apply when
1803   // the alias is used as the source option for rsync or sql-sync.
1804   if (!empty($prefix)) {
1805     $special_contexts = drush_get_special_keys();
1806     foreach ($special_contexts as $option_name) {
1807       if (array_key_exists($prefix . $option_name, $site_alias_settings)) {
1808         $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];
1809       }
1810     }
1811   }
1812   // Transfer all options from the site alias to the drush options
1813   // in the 'alias' context.
1814   foreach ($site_alias_settings as $key => $value) {
1815     // Special handling for path aliases:
1816     if ($key == "path-aliases") {
1817       $path_aliases = $value;
1818       foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) {
1819         if (array_key_exists($path_key, $path_aliases)) {
1820           // Evaluate the path value, and substitute any path references found.
1821           // ex: '%dump-dir' => '%root/dumps' will store sql-dumps in the folder
1822           // 'dumps' in the Drupal root folder for the site.
1823           $evaluated_path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path_aliases[$path_key]);
1824           $options[$prefix . substr($path_key, 1)] = $evaluated_path;
1825         }
1826       }
1827     }
1828     // Special handling for command-specific
1829     elseif ($key == "command-specific") {
1830       $options[$key] = $value;
1831     }
1832     elseif (!in_array($key, $skip_list)) {
1833       $options[$prefix . $key] = $value;
1834     }
1835   }
1836   drush_set_config_options('alias', $options);
1837 }
1838
1839 /**
1840  * Call prior to drush_sitealias_evaluate_path to insure
1841  * that any site-specific aliases associated with any
1842  * local site in $path are defined.
1843  */
1844 function _drush_sitealias_preflight_path($path) {
1845   $alias = NULL;
1846   // Parse site aliases if there is a colon in the path
1847   // We allow:
1848   //   @alias:/path
1849   //   machine.domain.com:/path
1850   //   machine:/path
1851   // Note that paths in the form "c:/path" are converted to
1852   // "/cygdrive/c/path" later; we do not want them to confuse
1853   // us here, so we skip paths that start with a single character
1854   // before the colon if we are running on Windows.  Single-character
1855   // machine names are allowed in Linux only.
1856   $colon_pos = strpos($path, ':');
1857   if ($colon_pos > (drush_is_windows("LOCAL") ? 1 : 0)) {
1858     $alias = substr($path, 0, $colon_pos);
1859     $path = substr($path, $colon_pos + 1);
1860     $site_alias_settings = drush_sitealias_get_record($alias);
1861     if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
1862       return NULL;
1863     }
1864     $machine = $alias;
1865   }
1866   else {
1867     $machine = '';
1868     // if the path is a site alias or a local site...
1869     $site_alias_settings = drush_sitealias_get_record($path);
1870     if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
1871       return NULL;
1872     }
1873     if (!empty($site_alias_settings) || drush_is_local_host($path)) {
1874       $alias = $path;
1875       $path = '';
1876     }
1877   }
1878   return array('alias' => $alias, 'path' => $path, 'machine' => $machine);
1879 }
1880
1881 /**
1882  * Given a properly-escaped options string, replace any occurance of
1883  * %files and so on embedded inside it with its corresponding path.
1884  */
1885 function drush_sitealias_evaluate_paths_in_options($option_string) {
1886   $path_aliases = _core_path_aliases();
1887   return str_replace(array_keys($path_aliases), array_values($path_aliases), $option_string);
1888 }
1889
1890 /**
1891  * Evaluate a path from its shorthand form to a literal path
1892  * usable by rsync.
1893  *
1894  * A path is "machine:/path" or "machine:path" or "/path" or "path".
1895  * 'machine' might instead be an alias record, or the name
1896  * of a site in the 'sites' folder.  'path' might be (or contain)
1897  * '%root' or some other path alias.  This function will examine
1898  * all components of the path and evaluate them as necessary to
1899  * come to the final path.
1900  *
1901  * @param path
1902  *   The path to evaluate
1903  * @param additional_options
1904  *   An array of options that overrides whatever was passed in on
1905  *   the command line (like the 'process' context, but only for
1906  *   the scope of this one call).
1907  * @param local_only
1908  *   If TRUE, force an error if the provided path points to a remote
1909  *   machine.
1910  * @param os
1911  *   This should be the local system os, unless evaluate path is
1912  *   being called for rsync, in which case it should be "CWRSYNC"
1913  *   if cwrsync is being used, or "rsync" to automatically select
1914  *   between "LOCAL" and "CWRSYNC" based on the platform.
1915  * @return
1916  *   The site record for the machine specified in the path, if any,
1917  *   with the path to pass to rsync (including the machine specifier)
1918  *   in the 'evaluated-path' item.
1919  */
1920 function drush_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE, $os = NULL, $command_specific_prefix = '') {
1921   $site_alias_settings = array();
1922   $path_aliases = array();
1923   $remote_user = '';
1924
1925   $preflight = _drush_sitealias_preflight_path($path);
1926   if (!isset($preflight)) {
1927     return NULL;
1928   }
1929
1930   $alias = $preflight['alias'];
1931   $path = $preflight['path'];
1932   $machine = $preflight['machine'];
1933
1934   if (isset($alias)) {
1935     // Note that the alias settings may have an 'os' component, but we do
1936     // not want to use it here.  The paths passed to rsync should always be
1937     // escaped per the LOCAL rules, without regard to the remote platform type.
1938     $site_alias_settings = drush_sitealias_get_record($alias);
1939     if (!empty($command_specific_prefix)) {
1940       drush_command_set_command_specific_options($command_specific_prefix);
1941       drush_sitealias_command_default_options($site_alias_settings, $command_specific_prefix);
1942     }
1943   }
1944
1945   if (!empty($site_alias_settings)) {
1946     if ($local_only && array_key_exists('remote-host', $site_alias_settings)) {
1947       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."));
1948     }
1949
1950     // Apply any options from this alias that might affect our rsync
1951     drush_sitealias_set_alias_context($site_alias_settings);
1952
1953     // Use 'remote-host' from settings if available; otherwise site is local
1954     if (drush_sitealias_is_remote_site($site_alias_settings)) {
1955       $machine = drush_remote_host($site_alias_settings);
1956     }
1957     else {
1958       $machine = '';
1959     }
1960   }
1961   else {
1962     // Strip the machine portion of the path if the
1963     // alias points to the local machine.
1964     if (drush_is_local_host($machine)) {
1965       $machine = '';
1966     }
1967     else {
1968       $machine = "$remote_user$machine";
1969     }
1970   }
1971
1972   // TOD:  The code below is a little rube-goldberg-ish, and needs to be
1973   // reworked.  core-rsync will call this function twice: once to
1974   // evaluate the destination, and then again to evaluate the source.  Things
1975   // get odd with --exclude-paths, especially in conjunction with command-specific
1976   // and the --exclude-files option.  @see testCommandSpecific()
1977
1978   // If the --exclude-other-sites option is specified, then
1979   // convert that into --include-paths='%site' and --exclude-sites.
1980   if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_context('exclude-other-sites-processed', FALSE)) {
1981     $include_path_option = drush_get_option_override($additional_options, 'include-paths', '');
1982     $additional_options['include-paths'] = '%site';
1983     if (!empty($include_path_option)) {
1984       // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR.
1985       $additional_options['include-paths'] .= PATH_SEPARATOR . $include_path_option;
1986     }
1987     $additional_options['exclude-sites'] = TRUE;
1988     drush_set_context('exclude-other-sites-processed', TRUE);
1989   }
1990   else {
1991     unset($additional_options['include-paths']);
1992   }
1993   // If the --exclude-files option is specified, then
1994   // convert that into --exclude-paths='%files'.
1995   if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) {
1996     $exclude_path_option = drush_get_option_override($additional_options, 'exclude-paths', '');
1997     $additional_options['exclude-paths'] = '%files';
1998     if (!empty($exclude_path_option)) {
1999       // We use PATH_SEPARATOR here because we are later going to explicitly explode() this variable using PATH_SEPARATOR.
2000       $additional_options['exclude-paths'] .= PATH_SEPARATOR . $exclude_path_option;
2001     }
2002     $additional_options['exclude-files-processed'] = TRUE;
2003   }
2004   else {
2005     unset($additional_options['exclude-paths']);
2006   }
2007
2008   // If there was no site specification given, and the
2009   // machine is local, then try to look
2010   // up an alias record for the default drush site.
2011   if (empty($site_alias_settings) && empty($machine)) {
2012     $drush_uri = drush_get_context('DRUSH_SELECTED_URI', 'default');
2013     $site_alias_settings = drush_sitealias_get_record($drush_uri);
2014   }
2015
2016   // Always add transient defaults
2017   _drush_sitealias_add_transient_defaults($site_alias_settings);
2018
2019   // The $resolve_path variable is used by drush_sitealias_resolve_path_references
2020   // to test to see if there are any path references such as %site or %files
2021   // in it, so that resolution is only done if the path alias is referenced.
2022   // Therefore, we can concatenate without worrying too much about the structure of
2023   // this variable's contents.
2024   $include_path = drush_get_option_override($additional_options, 'include-paths', '');
2025   $exclude_path = drush_get_option_override($additional_options, 'exclude-paths', '');
2026   if (is_array($include_path)) {
2027     $include_path = implode('/', $include_path);
2028   }
2029   if (is_array($exclude_path)) {
2030     $include_path = implode('/', $exclude_path);
2031   }
2032   $resolve_path = "$path/$include_path/$exclude_path";
2033   // Resolve path aliases such as %files, if any exist in the path
2034   if (!empty($resolve_path)) {
2035     drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path);
2036   }
2037
2038   if (array_key_exists('path-aliases', $site_alias_settings)) {
2039     $path_aliases = $site_alias_settings['path-aliases'];
2040   }
2041
2042   // Get the 'root' setting from the alias; if it does not
2043   // exist, then get the root from the bootstrapped site.
2044   if (array_key_exists('root', $site_alias_settings)) {
2045     $drupal_root = $site_alias_settings['root'];
2046   }
2047   elseif (!drush_sitealias_is_remote_site($site_alias_settings)) {
2048     drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
2049     $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
2050   }
2051   if (empty($drupal_root)) {
2052     $drupal_root = '';
2053   }
2054   else {
2055     // Add a slash to the end of the drupal root, as below.
2056     $drupal_root = drush_trim_path($drupal_root) . "/";
2057   }
2058   $full_path_aliases = $path_aliases;
2059   foreach ($full_path_aliases as $key => $value) {
2060     // Expand all relative path aliases to be based off of the Drupal root
2061     if (!drush_is_absolute_path($value, "LOCAL") && ($key != '%root')) {
2062       $full_path_aliases[$key] = $drupal_root . $value;
2063     }
2064     // We do not want slashes on the end of our path aliases.
2065     $full_path_aliases[$key] = drush_trim_path($full_path_aliases[$key]);
2066   }
2067
2068   // Fill in path aliases in the path, the include path and the exclude path.
2069   $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path);
2070   if (!empty($include_path)) {
2071     drush_set_option('include-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path));
2072   }
2073   if (!empty($exclude_path)) {
2074     drush_set_option('exclude-paths', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path));
2075   }
2076   // Next make the rsync path, which includes the machine
2077   // and path components together.
2078   // First make empty paths or relative paths start from the drupal root.
2079   if (empty($path) || (!drush_is_absolute_path($path, "LOCAL"))) {
2080     $path = $drupal_root . $path;
2081   }
2082   // When calculating a path for use with rsync, we must correct
2083   // absolute paths in the form c:\path when cwrsync is in use.
2084   $path = drush_correct_absolute_path_for_exec($path, $os);
2085
2086   // If there is a $machine component, to the path, then
2087   // add it to the beginning
2088   $evaluated_path = drush_escapeshellarg($path, $os);
2089   if (!empty($machine)) {
2090     $evaluated_path = $machine . ':' . $evaluated_path;
2091   }
2092
2093   //
2094   // Add our result paths:
2095   //
2096   //    evaluated-path:         machine:/path
2097   //    server-component:       machine
2098   //    path-component:         :/path
2099   //    path:                   /path
2100   //    user-path:              path (as specified in input parameter)
2101   //
2102   $site_alias_settings['evaluated-path'] = $evaluated_path;
2103   if (!empty($machine)) {
2104     $site_alias_settings['server-component'] = $machine;
2105   }
2106   $site_alias_settings['path-component'] = (!empty($path) ? ':' . $path : '');
2107   $site_alias_settings['path'] = $path;
2108   $site_alias_settings['user-path'] = $preflight['path'];
2109
2110   return $site_alias_settings;
2111 }
2112
2113 /**
2114  * Option keys used for site selection.
2115  */
2116 function drush_sitealias_site_selection_keys() {
2117   return array('remote-host', 'remote-user', 'ssh-options', '#name', 'os');
2118 }
2119
2120
2121 function sitealias_find_local_drupal_root($site_list) {
2122   $drupal_root = NULL;
2123
2124   foreach ($site_list as $site) {
2125     if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) {
2126       $drupal_root = $site['root'];
2127     }
2128   }
2129
2130   return $drupal_root;
2131 }
2132
2133
2134 /**
2135  * Helper function to obtain the keys' names that need special handling in certain
2136  * cases.
2137  * @return
2138  *   A non-associative array containing the needed keys' names.
2139  */
2140 function drush_get_special_keys() {
2141   $special_keys = array(
2142     'command-specific',
2143     'site-aliases',
2144   );
2145   return $special_keys;
2146 }
2147
2148 /**
2149  * Read the tmp file where the persistent site setting is stored.
2150  *
2151  * @return string
2152  *   A valid site specification.
2153  */
2154 function drush_sitealias_site_get() {
2155   if (($filename = drush_sitealias_get_envar_filename()) && file_exists($filename)) {
2156     $site = file_get_contents($filename);
2157     return $site;
2158   }
2159   else {
2160     return FALSE;
2161   }
2162 }
2163
2164 /**
2165  * Un-set the currently use'd site alias.
2166  */
2167 function drush_sitealias_site_clear() {
2168   if ($filename = drush_sitealias_get_envar_filename()) {
2169     return drush_delete_dir($filename);
2170   }
2171   return FALSE;
2172 }
2173
2174 /**
2175  * Returns the filename for the file that stores the DRUPAL_SITE variable.
2176  *
2177  * @param string $filename_prefix
2178  *   An arbitrary string to prefix the filename with.
2179  *
2180  * @return string|false
2181  *   Returns the full path to temp file if possible, or FALSE if not.
2182  */
2183 function drush_sitealias_get_envar_filename($filename_prefix = 'drush-drupal-site-') {
2184   $shell_pid = getenv('DRUSH_SHELL_PID');
2185   if (!$shell_pid && function_exists('posix_getppid')) {
2186     $shell_pid = posix_getppid();
2187   }
2188   if (!$shell_pid) {
2189     return FALSE;
2190   }
2191
2192   $tmp = getenv('TMPDIR') ? getenv('TMPDIR') : '/tmp';
2193   $username = drush_get_username();
2194
2195   return "{$tmp}/drush-env-{$username}/{$filename_prefix}" . $shell_pid;
2196 }
2197
2198 /**
2199  * Cache the specified alias in the alias path cache.  The
2200  * alias path cache creates a lookup from the site folder
2201  * (/path/to/drupal/sites/default) to the provided alias record.
2202  *
2203  * Only the name of the alias and the path to the file it
2204  * is stored in is cached; when it is retrieved, it is
2205  * loaded directly from the correct file.
2206  */
2207 function drush_sitealias_cache_alias_by_path($alias_record) {
2208   if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) {
2209     $path = drush_sitealias_local_site_path($alias_record);
2210     if ($path) {
2211       $cid = drush_get_cid('alias-path-', array(), array($path));
2212       $alias_path_data = array(
2213         '#name' => $alias_record['#name'],
2214         '#file' => $alias_record['#file'],
2215       );
2216       drush_cache_set($cid, $alias_path_data);
2217     }
2218   }
2219 }
2220
2221 /**
2222  * Look for a defined alias that points to the specified
2223  * site directory.  The cache is tested first; if nothing
2224  * is cached, then an exhaustive search is done for the
2225  * specified site.  If the exhaustive search returns a
2226  * match, then it is cached.
2227  *
2228  * @param $path
2229  *   /path/to/drupal/sites/default
2230  * @return
2231  *   An alias record for the provided path
2232  */
2233 function drush_sitealias_lookup_alias_by_path($path, $allow_best_match=FALSE) {
2234   $result = drush_sitealias_quick_lookup_cached_alias_by_path($path);
2235   $fallback = array();
2236   if (empty($result)) {
2237     $aliases = _drush_sitealias_find_and_load_all_aliases();
2238     foreach ($aliases as $name => $alias_record) {
2239       if (!isset($alias_record['remote-host']) && isset($alias_record['root']) && isset($alias_record['uri']) && isset($alias_record['#name']) && isset($alias_record['#file'])) {
2240         if ($path == drush_sitealias_local_site_path($alias_record)) {
2241           $result = $alias_record;
2242           break;
2243         }
2244         if (substr($path, 0, strlen($alias_record['root'])) == $alias_record['root']) {
2245           $fallback = $alias_record;
2246         }
2247       }
2248     }
2249   }
2250   if (empty($result) && $allow_best_match) {
2251     $result = $fallback;
2252   }
2253   if (!empty($result)) {
2254     _drush_sitealias_add_inherited_values_to_record($result);
2255     drush_sitealias_cache_alias_by_path($result);
2256   }
2257   return $result;
2258 }
2259
2260 /**
2261  * Look for a cached alias that points to the specified
2262  * site directory.  Nothing is returned if there is no
2263  * matching cached alias.
2264  *
2265  * @param $path
2266  *   /path/to/drupal/sites/default
2267  * @return
2268  *   An alias record for the provided path
2269  */
2270 function drush_sitealias_quick_lookup_cached_alias_by_path($path) {
2271   $alias_record = array();
2272   $cid = drush_get_cid('alias-path-', array(), array($path));
2273   $alias_path_cache = drush_cache_get($cid);
2274   if (isset($alias_path_cache->data)) {
2275     $alias_name = $alias_path_cache->data['#name'];
2276     $alias_file = $alias_path_cache->data['#file'];
2277
2278     $alias_record = _drush_sitealias_find_and_load_alias_from_file($alias_name, array($alias_file));
2279     _drush_sitealias_add_inherited_values_to_record($alias_record);
2280     $alias_record['#name'] = $alias_name;
2281   }
2282   return $alias_record;
2283 }
2284
2285 /**
2286  * Return the site root, if there is one in the record.
2287  */
2288 function drush_sitealias_get_root($alias_record) {
2289   return array_key_exists('root', $alias_record) ? $alias_record['root'] : NULL;
2290 }
2291
2292 /**
2293  * Decide on which side to run a core-rsync.
2294  *
2295  * @param $source
2296  * @param $destination
2297  * @param $runner Where to run the rsync operation: 'destination', 'source',
2298  *   'auto' ('destination' if both are remote, otherwise '@self') or FALSE (@self)
2299  * @return mixed
2300  */
2301 function drush_get_runner($source, $destination, $runner = FALSE) {
2302   if (is_string($source)) {
2303     $source = drush_sitealias_get_record($site);
2304   }
2305   if (is_string($destination)) {
2306     $destination = drush_sitealias_get_record($destination);
2307   }
2308
2309   // If both sites are remote, and --runner=auto, then we'll use the destination site.
2310   if (drush_sitealias_is_remote_site($source) && drush_sitealias_is_remote_site($destination)) {
2311     if ($runner == 'auto') {
2312       $runner = 'destination';
2313     }
2314   }
2315
2316   // If the user explicitly requests a remote site, then return the selected one.
2317   if ($runner == 'destination') {
2318     return "@" . $destination['#name'];
2319   }
2320   if ($runner == 'source') {
2321     return "@" . $source['#name'];
2322   }
2323
2324   // Default to running rsync locally. When in doubt, local is best, because
2325   // we can always resolve aliases here.
2326   return '@self';
2327 }