sitealias.inc 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669
  1. <?php
  2. /**
  3. * @file
  4. * The site alias API.
  5. *
  6. * Run commands on remote server(s).
  7. * @see example.aliases.drushrc.php
  8. * @see http://drupal.org/node/670460
  9. */
  10. /**
  11. * Check to see if the first command-line arg or the
  12. * -l option is a site alias; if it is, copy its record
  13. * values to the 'alias' context.
  14. *
  15. * @return boolean
  16. * TRUE if a site alias was found and processed.
  17. */
  18. function drush_sitealias_check_arg() {
  19. $args = drush_get_arguments();
  20. // Test to see if the first arg is a site specification
  21. if (_drush_sitealias_set_context_by_name($args[0])) {
  22. array_shift($args);
  23. // We only need to expand the site specification
  24. // once, then we are done.
  25. drush_set_arguments($args);
  26. return TRUE;
  27. }
  28. // Return false to indicate that no site alias was specified.
  29. return FALSE;
  30. }
  31. /**
  32. * Given a list of alias records, shorten the name used if possible
  33. */
  34. function drush_sitealias_simplify_names($site_list) {
  35. $result = array();
  36. foreach ($site_list as $original_name => $alias_record) {
  37. $adjusted_name = $alias_record['#name'];
  38. $hashpos = strpos($original_name, '#');
  39. if ($hashpos !== FALSE) {
  40. $adjusted_name = substr($original_name, $hashpos);
  41. if (array_key_exists('remote-host', $alias_record)) {
  42. $adjusted_name = $alias_record['remote-host'] . $adjusted_name;
  43. }
  44. }
  45. $result[$adjusted_name] = $alias_record;
  46. }
  47. return $result;
  48. }
  49. /**
  50. * Given an array of site specifications, resolve each one in turn and
  51. * return an array of alias records. If you only want a single record,
  52. * it is preferable to simply call drush_sitealias_get_record directly.
  53. *
  54. * @param $site_specifications
  55. * An array of site specificatins. @see drush_sitealias_get_record
  56. * @return
  57. * An array of alias records
  58. */
  59. function drush_sitealias_resolve_sitespecs($site_specifications) {
  60. $result_list = array();
  61. if (!empty($site_specifications)) {
  62. foreach ($site_specifications as $site) {
  63. $alias_record = drush_sitealias_get_record($site);
  64. $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($alias_record));
  65. }
  66. }
  67. return $result_list;
  68. }
  69. /**
  70. * Get a site alias record given an alias name or site specification.
  71. *
  72. * If it is the name of a site alias, return the alias record from
  73. * the site aliases array.
  74. *
  75. * If it is the name of a folder in the 'sites' folder, construct
  76. * an alias record from values stored in settings.php.
  77. *
  78. * If it is a site specification, construct an alias record from the
  79. * values in the specification.
  80. *
  81. * Site specifications come in several forms:
  82. *
  83. * 1.) /path/to/drupal#sitename
  84. * 2.) user@server/path/to/drupal#sitename
  85. * 3.) user@server/path/to/drupal (sitename == server)
  86. * 4.) user@server#sitename (only if $option['r'] set in some drushrc file on server)
  87. * 5.) #sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
  88. * 6.) sitename (only if $option['r'] already set, and 'sitename' is a folder in $option['r']/sites)
  89. *
  90. * Note that in the case of the first four forms, it is also possible
  91. * to add additional site variable to the specification using uri query
  92. * syntax. For example:
  93. *
  94. * user@server/path/to/drupal?db-url=...#sitename
  95. *
  96. * @param alias
  97. * An alias name or site specification
  98. * @return array
  99. * An alias record, or empty if none found.
  100. */
  101. function drush_sitealias_get_record($alias) {
  102. // Check to see if the alias contains commas. If it does, then
  103. // we will go ahead and make a site list record
  104. $alias_record = array();
  105. if (strpos($alias, ',') !== false) {
  106. // TODO: If the site list contains any site lists, or site
  107. // search paths, then we should expand those and merge them
  108. // into this list longhand.
  109. $alias_record['site-list'] = explode(',', $alias);
  110. }
  111. else {
  112. $alias_record = _drush_sitealias_get_record($alias);
  113. }
  114. if (!empty($alias_record)) {
  115. if (!array_key_exists('#name', $alias_record)) {
  116. $alias_record['#name'] = drush_sitealias_uri_to_site_dir($alias);
  117. }
  118. // Handle nested alias definitions and command-specific options.
  119. drush_set_config_special_contexts($alias_record);
  120. }
  121. return $alias_record;
  122. }
  123. /**
  124. * This is a continuation of drush_sitealias_get_record, above. It is
  125. * not intended to be called directly.
  126. */
  127. function _drush_sitealias_get_record($alias, $alias_context = NULL) {
  128. // Before we do anything else, load $alias if it needs to be loaded
  129. _drush_sitealias_load_alias($alias, $alias_context);
  130. // Check to see if the provided parameter is in fact a defined alias.
  131. $all_site_aliases =& drush_get_context('site-aliases');
  132. if (array_key_exists($alias, $all_site_aliases)) {
  133. $alias_record = $all_site_aliases[$alias];
  134. }
  135. // If the parameter is not an alias, then it is some form of
  136. // site specification (or it is nothing at all)
  137. else {
  138. if (isset($alias)) {
  139. // Cases 1.) - 4.):
  140. // We will check for a site specification if the alias has at least
  141. // two characters from the set '@', '/', '#'.
  142. if ((strpos($alias, '@') === FALSE ? 0 : 1) + (strpos($alias, '/') === FALSE ? 0 : 1) + (strpos($alias, '#') === FALSE ? 0 : 1) >= 2) {
  143. if ((substr($alias,0,7) != 'http://') && (substr($alias,0,1) != '/')) {
  144. // Add on a scheme so that "user:pass@server" will always parse correctly
  145. $parsed = parse_url('http://' . $alias);
  146. }
  147. else {
  148. $parsed = parse_url($alias);
  149. }
  150. // Copy various parts of the parsed URL into the appropriate records of the alias record
  151. foreach (array('user' => 'remote-user', 'pass' => 'remote-pass', 'host' => 'remote-host', 'fragment' => 'uri', 'path' => 'root') as $url_key => $option_key) {
  152. if (array_key_exists($url_key, $parsed)) {
  153. _drush_sitealias_set_record_element($alias_record, $option_key, $parsed[$url_key]);
  154. }
  155. }
  156. // If the site specification has a query, also set the query items
  157. // in the alias record. This allows passing db_url as part of the
  158. // site specification, for example.
  159. if (array_key_exists('query', $parsed)) {
  160. foreach (explode('&', $parsed['query']) as $query_arg) {
  161. $query_components = explode('=', $query_arg);
  162. _drush_sitealias_set_record_element($alias_record, urldecode($query_components[0]), urldecode($query_components[1]));
  163. }
  164. }
  165. // Case 3.): If the URL contains a 'host' portion but no fragment, then set the uri to the host
  166. // Note: We presume that 'server' is the best default for case 3; without this code, the default would
  167. // be whatever is set in $options['l'] on the target machine's drushrc.php settings file.
  168. if (array_key_exists('host', $parsed) && !array_key_exists('fragment', $parsed)) {
  169. $alias_record['uri'] = $parsed['host'];
  170. }
  171. // Special checking: relative aliases embedded in a path
  172. $relative_alias_pos = strpos($alias_record['root'], '/@');
  173. if ($relative_alias_pos !== FALSE) {
  174. // Special checking: /path/@sites
  175. $base = substr($alias_record['root'], 0, $relative_alias_pos);
  176. $relative_alias = substr($alias_record['root'], $relative_alias_pos + 1);
  177. if (drush_valid_drupal_root($base) || ($relative_alias == '@sites')) {
  178. drush_sitealias_create_sites_alias($base);
  179. $alias_record = drush_sitealias_get_record($relative_alias);
  180. }
  181. else {
  182. $alias_record = array();
  183. }
  184. }
  185. }
  186. else {
  187. // Case 5.) and 6.):
  188. // If the alias is the name of a folder in the 'sites' directory,
  189. // then use it as a local site specification.
  190. $alias_record = _drush_sitealias_find_record_for_local_site($alias);
  191. }
  192. }
  193. }
  194. if (!empty($alias_record)) {
  195. // Load the drush config file if there is one associated with this alias
  196. if (!isset($alias_record['remote']) && !isset($alias_record['loaded-config'])) {
  197. if (array_key_exists('root', $alias_record)) {
  198. drush_sitealias_add_to_alias_path($alias_record['root']);
  199. }
  200. $alias_site_dir = drush_sitealias_local_site_path($alias_record);
  201. if (isset($alias_site_dir)) {
  202. // Add the sites folder of this site to the alias search path list
  203. drush_sitealias_add_to_alias_path($alias_site_dir);
  204. if (!isset($alias_record['config'])) {
  205. $alias_record['config'] = realpath($alias_site_dir . '/drushrc.php');
  206. }
  207. }
  208. if (isset($alias_record['config']) && file_exists($alias_record['config'])) {
  209. drush_load_config_file('site', $alias_record['config']);
  210. $alias_record['loaded-config'] = TRUE;
  211. }
  212. unset($alias_record['config']);
  213. }
  214. // Add the static defaults
  215. _drush_sitealias_add_static_defaults($alias_record);
  216. // Cache the result with all of its calculated values
  217. $all_site_aliases[$alias] = $alias_record;
  218. }
  219. return $alias_record;
  220. }
  221. /**
  222. * Add a path to the array of paths where alias files are searched for.
  223. *
  224. * @param $add_path
  225. * A path to add to the search path (or NULL to not add any).
  226. * Once added, the new path will remain available until drush
  227. * exits.
  228. * @return
  229. * An array of paths containing all values added so far
  230. */
  231. function drush_sitealias_add_to_alias_path($add_path) {
  232. static $site_paths = array();
  233. if ($add_path != NULL) {
  234. if (!is_array($add_path)) {
  235. $add_path = explode(':', $add_path);
  236. }
  237. $site_paths = array_unique(array_merge($site_paths, $add_path));
  238. }
  239. return $site_paths;
  240. }
  241. /**
  242. * Return the array of paths where alias files are searched for.
  243. *
  244. * @param $alias_path_context
  245. * If the alias being looked up is part of a relative alias,
  246. * the alias path context specifies the context of the primary
  247. * alias the new alias is rooted from. Alias files stored in
  248. * the sites folder of this context, or inside the context itself
  249. * takes priority over any other search path that might define
  250. * a similarly-named alias. In this way, multiple sites can define
  251. * a '@peer' alias.
  252. * @return
  253. * An array of paths
  254. */
  255. function drush_sitealias_alias_path($alias_path_context = NULL) {
  256. if (isset($alias_path_context)) {
  257. return array(drush_sitealias_local_site_path($alias_path_context));
  258. }
  259. else {
  260. // We get the current list of site paths by adding NULL
  261. // (nothing) to the path list, which is a no-op
  262. $site_paths = drush_sitealias_add_to_alias_path(NULL);
  263. // If the user defined the root of a drupal site, then also
  264. // look for alias files there.
  265. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  266. if (empty($drupal_root)) {
  267. $drupal_root = drush_get_option(array('root', 'r'), NULL);
  268. }
  269. if (isset($drupal_root)) {
  270. $site_paths[] = $drupal_root;
  271. }
  272. $alias_path = (array) drush_get_option('alias-path', array());
  273. if (empty($alias_path)) {
  274. $alias_path[] = drush_get_context('ETC_PREFIX', '') . '/etc/drush';
  275. $alias_path[] = dirname(__FILE__) . '/..';
  276. $alias_path[] = dirname(__FILE__) . '/../aliases';
  277. if(!is_null(drush_server_home())) {
  278. $alias_path[] = drush_server_home() . '/.drush';
  279. }
  280. }
  281. return array_unique(array_merge($alias_path, $site_paths));
  282. }
  283. }
  284. /**
  285. * Return the full path to the site directory of the
  286. * given alias record.
  287. *
  288. * @param $alias_record
  289. * The alias record
  290. * @return
  291. * The path to the site directory of the associated
  292. * alias record, or NULL if the record is not a local site.
  293. */
  294. function drush_sitealias_local_site_path($alias_record) {
  295. $result = NULL;
  296. if (isset($alias_record['uri']) && isset($alias_record['root']) && !isset($alias_record['remote-host'])) {
  297. $result = realpath($alias_record['root'] . '/sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri']));
  298. }
  299. return $result;
  300. }
  301. /**
  302. * Check and see if an alias definition for $alias is available.
  303. * If it is, load it into the list of aliases cached in the
  304. * 'site-aliases' context.
  305. *
  306. * @param $alias
  307. * The name of the alias to load in ordinary form ('@name')
  308. * @param $alias_path_context
  309. * When looking up a relative alias, the alias path context is
  310. * the primary alias that we will start our search from.
  311. */
  312. function _drush_sitealias_load_alias($alias, $alias_path_context = NULL) {
  313. $all_site_aliases = drush_get_context('site-aliases');
  314. $result = array();
  315. // Check to see if this is a relative alias ('@site/@peer')
  316. $relative_alias_pos = strpos($alias, '/@');
  317. if ($relative_alias_pos !== false) {
  318. $primary_alias = substr($alias,0,$relative_alias_pos);
  319. $relative_alias = substr($alias,$relative_alias_pos + 1);
  320. $primary_record = drush_sitealias_get_record($primary_alias);
  321. _drush_sitealias_find_and_load_alias(substr($relative_alias,1), $primary_record);
  322. $result = drush_sitealias_get_record($relative_alias);
  323. if (!empty($result)) {
  324. if (array_key_exists('inherited', $result)) {
  325. $result = array_merge($primary_record, $result);
  326. }
  327. $result['#name'] = $relative_alias;
  328. _drush_sitealias_cache_alias(substr($alias, 1), $result);
  329. }
  330. }
  331. else {
  332. // Only aliases--those named entities that begin with '@'--can be loaded this way.
  333. // We also skip any alias that has already been loaded.
  334. if ((substr($alias,0,1) == '@') && !array_key_exists($alias,$all_site_aliases)) {
  335. drush_log(dt('Load alias !alias', array('!alias' => $alias)));
  336. $aliasname = substr($alias,1);
  337. $result = _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context);
  338. if (!empty($result)) {
  339. $alias_options = array('site-aliases' => array($aliasname => $result));
  340. _drush_sitealias_add_inherited_values($alias_options['site-aliases']);
  341. drush_set_config_special_contexts($alias_options);
  342. }
  343. }
  344. }
  345. return $result;
  346. }
  347. /**
  348. * Load every alias file that can be found anywhere in the
  349. * alias search path.
  350. */
  351. function drush_sitealias_load_all($resolve_parent = TRUE) {
  352. $result = _drush_sitealias_find_and_load_alias(NULL);
  353. if (!empty($result) && ($resolve_parent == TRUE)) {
  354. // If any aliases were returned, then check for
  355. // inheritance and then store the aliases into the
  356. // alias cache
  357. _drush_sitealias_add_inherited_values($result);
  358. $alias_options = array('site-aliases' => $result);
  359. drush_set_config_special_contexts($alias_options);
  360. }
  361. }
  362. /**
  363. * Worker function called by _drush_sitealias_load_alias and
  364. * drush_sitealias_load_all. Traverses the alias search path
  365. * and finds the specified alias record.
  366. *
  367. * @param $aliasname
  368. * The name of the alias without the leading '@' (i.e. '#name')
  369. * or NULL to load every alias found in every alias file.
  370. * @param $alias_path_context
  371. * When looking up a relative alias, the alias path context is
  372. * the primary alias that we will start our search from.
  373. * @return
  374. * An empty array if nothing was loaded. If $aliasname is
  375. * not null, then the array returned is the alias record for
  376. * $aliasname. If $aliasname is NULL, then the array returned
  377. * is a $kay => $value pair of alias names and alias records
  378. * loaded.
  379. */
  380. function _drush_sitealias_find_and_load_alias($aliasname, $alias_path_context = NULL) {
  381. $result = array();
  382. // Special checking for '@sites' alias
  383. if ($aliasname == 'sites') {
  384. $drupal_root = NULL;
  385. if ($alias_path_context != null) {
  386. if (array_key_exists('root', $alias_path_context) && !array_key_exists('remote-host', $alias_path_context)) {
  387. $drupal_root = $alias_path_context['root'];
  388. }
  389. }
  390. else {
  391. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  392. if ($drupal_root == NULL) {
  393. $drupal_root = drush_get_option(array('r', 'root'), drush_locate_root());
  394. }
  395. }
  396. if (isset($drupal_root) && !is_array($drupal_root)) {
  397. drush_sitealias_create_sites_alias($drupal_root);
  398. }
  399. }
  400. // The alias path is a list of folders to search for alias settings files
  401. $alias_path = drush_sitealias_alias_path($alias_path_context);
  402. // $alias_files contains a list of filename patterns
  403. // to search for. We will find any matching file in
  404. // any folder in the alias path. The directory scan
  405. // is not deep, though; only files immediately in the
  406. // search path are considered.
  407. $alias_files = array('/.*aliases\.drushrc\.php$/');
  408. if ($aliasname == NULL) {
  409. $alias_files[] = '/.*\.alias\.drushrc\.php$/';
  410. }
  411. else {
  412. $alias_files[] = '/' . preg_quote($aliasname, '/') . '\.alias\.drushrc\.php$/';
  413. }
  414. // Search each path in turn
  415. foreach ($alias_path as $path) {
  416. // Find all of the matching files in this location
  417. $alias_files_to_consider = array();
  418. foreach ($alias_files as $file_pattern_to_search_for) {
  419. $alias_files_to_consider = array_merge($alias_files_to_consider, array_keys(drush_scan_directory($path, $file_pattern_to_search_for, array('.', '..', 'CVS'), 0, FALSE)));
  420. }
  421. // For every file that matches, check inside it for
  422. // an alias with a matching name.
  423. foreach ($alias_files_to_consider as $filename) {
  424. if (file_exists($filename)) {
  425. $aliases = $options = array();
  426. // silently ignore files we can't include
  427. if ((@include $filename) === FALSE) {
  428. drush_log(dt('Cannot open alias file "!alias", ignoring.', array('!alias' => realpath($filename))), 'bootstrap');
  429. continue;
  430. }
  431. unset($options['site-aliases']); // maybe unnecessary
  432. // If $aliases are not set, but $options are, then define one alias named
  433. // after the first word of the file, before '.alias.drushrc.php.
  434. if (empty($aliases) && !empty($options)) {
  435. $this_alias_name = substr(basename($filename),0,strpos(basename($filename),'.'));
  436. $aliases[$this_alias_name] = $options;
  437. $options = array();
  438. }
  439. // If this is a group alias file, then make an
  440. // implicit alias from the group name that contains
  441. // a site-list of all of the aliases in the file
  442. if (substr($filename, -20) == ".aliases.drushrc.php") {
  443. $group_name = basename($filename,".aliases.drushrc.php");
  444. if (!empty($aliases) && !array_key_exists($group_name, $aliases)) {
  445. $alias_names = array();
  446. foreach (array_keys($aliases) as $one_alias) {
  447. $alias_names[] = "@$group_name.$one_alias";
  448. $aliases["$group_name.$one_alias"] = $aliases[$one_alias];
  449. $aliases[$one_alias]["#hidden"] = TRUE;
  450. }
  451. $aliases[$group_name] = array('site-list' => implode(',', $alias_names));
  452. }
  453. }
  454. // If aliasname is NULL, then we will store
  455. // all $aliases into the alias cache
  456. $alias_recorded = FALSE;
  457. if ($aliasname == NULL) {
  458. if (!empty($aliases)) {
  459. if (!empty($options)) {
  460. foreach ($aliases as $name => $value) {
  461. $aliases[$name] = array_merge($options, $value);
  462. }
  463. $options = array();
  464. }
  465. foreach ($aliases as $name => $value) {
  466. _drush_sitealias_initialize_alias_record($aliases[$name]);
  467. }
  468. $result = array_merge($result, $aliases);
  469. $alias_recorded = TRUE;
  470. }
  471. }
  472. // If aliasname is not NULL, then we will store
  473. // only the named alias into the alias cache
  474. elseif ((isset($aliases)) && array_key_exists($aliasname, $aliases)) {
  475. drush_set_config_special_contexts($options); // maybe unnecessary
  476. $result = array_merge($options, $aliases[$aliasname]);
  477. _drush_sitealias_initialize_alias_record($result);
  478. $alias_recorded = TRUE;
  479. }
  480. // If we found at least one alias from this file
  481. // then record it in the drush-alias-files context.
  482. if ($alias_recorded) {
  483. $drush_alias_files = drush_get_context('drush-alias-files');
  484. if (!in_array($filename, $drush_alias_files)) {
  485. $drush_alias_files[] = $filename;
  486. }
  487. drush_set_context('drush-alias-files', $drush_alias_files);
  488. }
  489. }
  490. }
  491. }
  492. return $result;
  493. }
  494. /**
  495. * Check to see if there is a 'parent' item in the alias; if there is,
  496. * then load the parent alias record and overlay the entries in the
  497. * current alias record on top of the items from the parent record.
  498. *
  499. * @param $aliases
  500. * An array of alias records that are modified in-place.
  501. */
  502. function _drush_sitealias_add_inherited_values(&$aliases) {
  503. foreach ($aliases as $alias_name => $alias_value) {
  504. if (isset($alias_value['parent'])) {
  505. // Prevent circular references from causing an infinite loop
  506. _drush_sitealias_cache_alias($alias_name, array());
  507. // Fetch and merge in each parent
  508. foreach (explode(',', $alias_value['parent']) as $parent) {
  509. $parent_record = drush_sitealias_get_record($parent);
  510. unset($parent_record['#name']);
  511. unset($parent_record['#hidden']);
  512. $array_based_keys = array_merge(drush_get_special_keys(), array('path-aliases'));
  513. foreach ($array_based_keys as $array_based_key) {
  514. if (isset($aliases[$alias_name][$array_based_key]) && isset($parent_record[$array_based_key])) {
  515. $aliases[$alias_name][$array_based_key] = array_merge($parent_record[$array_based_key], $aliases[$alias_name][$array_based_key]);
  516. }
  517. }
  518. $aliases[$alias_name] = array_merge($parent_record, $aliases[$alias_name]);
  519. }
  520. unset($aliases[$alias_name]['parent']);
  521. }
  522. }
  523. }
  524. /**
  525. * Add an empty record for the specified alias name
  526. *
  527. * @param $alias_name
  528. * The name of the alias, without the leading "@"
  529. */
  530. function _drush_sitealias_cache_alias($alias_name, $alias_record) {
  531. $cache =& drush_get_context('site-aliases');
  532. // If the alias already exists in the cache, then merge
  533. // the new alias with the existing alias
  534. if (array_key_exists("@$alias_name", $cache)) {
  535. $alias_record = array_merge($cache["@$alias_name"], $alias_record);
  536. }
  537. $cache["@$alias_name"] = $alias_record;
  538. // If the alias record points at a local site, make sure
  539. // that both the drupal root and the site folder for that site
  540. // are added to the alias path, so that other alias files
  541. // stored in those locations become searchable.
  542. if (!array_key_exists('remote-host', $alias_record) && array_key_exists('root', $alias_record)) {
  543. drush_sitealias_add_to_alias_path($alias_record['root']);
  544. $site_dir = drush_sitealias_local_site_path($alias_record);
  545. if (isset($site_dir)) {
  546. drush_sitealias_add_to_alias_path($site_dir);
  547. }
  548. }
  549. }
  550. /**
  551. * If the alias record does not contain a 'databases' or 'db-url'
  552. * entry, then use backend invoke to look up the settings value
  553. * from the remote or local site. The 'db_url' form is preferred;
  554. * nothing is done if 'db_url' is not available (e.g. on a D7 site)
  555. *
  556. * @param $alias_record
  557. * The full alias record to populate with database settings
  558. */
  559. function drush_sitealias_add_db_url(&$alias_record) {
  560. if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
  561. $values = drush_invoke_sitealias_args($alias_record, "sql-conf", array(), array('db-url' => TRUE));
  562. if (isset($values['object']['db-url'])) {
  563. $alias_record['db-url'] = $values['object']['db-url'];
  564. }
  565. }
  566. }
  567. /**
  568. * Return the databases record from the alias record
  569. *
  570. * @param $alias_record
  571. * A record returned from drush_sitealias_get_record
  572. * @returns
  573. * A databases record (always in D7 format) or NULL
  574. * if the databases record could not be found.
  575. */
  576. function sitealias_get_databases_from_record(&$alias_record) {
  577. $altered_record = drush_sitealias_add_db_settings($alias_record);
  578. return array_key_exists('databases', $alias_record) ? $alias_record['databases'] : NULL;
  579. }
  580. /**
  581. * If the alias record does not contain a 'databases' or 'db-url'
  582. * entry, then use backend invoke to look up the settings value
  583. * from the remote or local site. The 'databases' form is
  584. * preferred; 'db_url' will be converted to 'databases' if necessary.
  585. *
  586. * @param $alias_record
  587. * The full alias record to populate with database settings
  588. */
  589. function drush_sitealias_add_db_settings(&$alias_record)
  590. {
  591. $altered_record = FALSE;
  592. // If the alias record does not have a defined 'databases' entry,
  593. // then we'll need to look one up
  594. if (!isset($alias_record['db-url']) && !isset($alias_record['databases']) && !isset($alias_record['site-list'])) {
  595. $values = drush_invoke_sitealias_args($alias_record, "sql-conf", array(), array('all' => TRUE));
  596. if (isset($values) && ($values['error_status'] == 0)) {
  597. $altered_record = TRUE;
  598. // If there are any special settings in the '@self' record returned by drush_invoke_sitealias_args,
  599. // then add those into our altered record as well
  600. if (array_key_exists('self', $values)) {
  601. $alias_record = array_merge($values['self'], $alias_record);
  602. }
  603. drush_sitealias_cache_db_settings($alias_record, $values['object']);
  604. }
  605. }
  606. return $altered_record;
  607. }
  608. function drush_sitealias_cache_db_settings(&$alias_record, $databases) {
  609. if (!empty($databases)) {
  610. $alias_record['databases'] = $databases;
  611. }
  612. // If the name is set, then re-cache the record after we fetch the databases
  613. if (array_key_exists('#name', $alias_record)) {
  614. $all_site_aliases =& drush_get_context('site-aliases');
  615. $all_site_aliases[$alias_record['#name']] = $alias_record;
  616. // Check and see if this record is a copy of 'self'
  617. if (($alias_record['#name'] != 'self') && array_key_exists('@self', $all_site_aliases) && ($all_site_aliases['@self']['#name'] == $alias_record['#name'])) {
  618. $all_site_aliases['@self'] = $alias_record;
  619. }
  620. }
  621. }
  622. /**
  623. * Check to see if we have already bootstrapped to a site.
  624. */
  625. function drush_sitealias_is_bootstrapped_site($alias_record) {
  626. if (!isset($alias_record['remote-host'])) {
  627. $self_record = drush_sitealias_get_record("@self");
  628. if (empty($self_record)) {
  629. // TODO: If we have not bootstrapped to a site yet, we could
  630. // perhaps bootstrap to $alias_record here.
  631. return FALSE;
  632. }
  633. elseif(($alias_record['root'] == $self_record['root']) && ($alias_record['uri'] == $self_record['uri'])) {
  634. return TRUE;
  635. }
  636. }
  637. return FALSE;
  638. }
  639. /**
  640. * If there are any path aliases (items beginning with "%") in the test
  641. * string, then resolve them as path aliases and add them to the provided
  642. * alias record.
  643. *
  644. * @param $alias_record
  645. * The full alias record to use in path alias expansion
  646. * @param $test_string
  647. * A slash-separated list of path aliases to resolve
  648. * e.g. "%files/%special".
  649. */
  650. function drush_sitealias_resolve_path_references(&$alias_record, $test_string = '') {
  651. // Convert the test string into an array of items, and
  652. // from this make a comma-separated list of projects
  653. // that we can pass to 'drush status'.
  654. $test_array = explode('/', $test_string);
  655. $project_array = array();
  656. foreach($test_array as $one_item) {
  657. if (substr($one_item,0,1) == '%') {
  658. $project_array[] = substr($one_item,1);
  659. }
  660. }
  661. // If we already have a path in the path aliases, then
  662. // there is no need to search for it remotely; we can remove
  663. // it from the project array.
  664. if (array_key_exists('path-aliases', $alias_record)) {
  665. foreach ($alias_record['path-aliases'] as $key => $value) {
  666. if (substr($key,0,1) == '%') {
  667. unset($project_array['%' . substr($key,1)]);
  668. }
  669. }
  670. }
  671. $project_list = implode(',', $project_array);
  672. if (!empty($project_array)) {
  673. // Optimization: if we're already bootstrapped to the
  674. // site specified by $alias_record, then we can just
  675. // call _core_site_status_table() rather than use backend invoke.
  676. if (drush_sitealias_is_bootstrapped_site($alias_record)) {
  677. // Make sure that we are bootstrapped at least to the 'site'
  678. // level, and include file.inc to insure that we have access
  679. // to the %file path.
  680. if (drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_SITE)) {
  681. include_once DRUPAL_ROOT . '/includes/file.inc';
  682. }
  683. $status_values = _core_site_status_table($project_list);
  684. }
  685. else {
  686. $values = drush_invoke_sitealias_args($alias_record, "status", array(), empty($project_list) ? array() : array('project' => $project_list));
  687. $status_values = $values['object'];
  688. }
  689. if (isset($status_values['%paths'])) {
  690. foreach ($status_values['%paths'] as $key => $path) {
  691. $alias_record['path-aliases'][$key] = $path;
  692. }
  693. }
  694. }
  695. }
  696. /**
  697. * Given an alias record that is a site list (contains a 'site-list' entry),
  698. * resolve all of the members of the site list and return them
  699. * is an array of alias records.
  700. *
  701. * @param $alias_record
  702. * The site list alias record array
  703. * @return
  704. * An array of individual site alias records
  705. */
  706. function drush_sitealias_resolve_sitelist($alias_record) {
  707. $result_list = array();
  708. if (isset($alias_record)) {
  709. if (array_key_exists('site-list', $alias_record)) {
  710. foreach ($alias_record['site-list'] as $sitespec) {
  711. $one_result = drush_sitealias_get_record($sitespec);
  712. $result_list = array_merge($result_list, drush_sitealias_resolve_sitelist($one_result));
  713. }
  714. }
  715. elseif (array_key_exists('#name', $alias_record)) {
  716. $result_list[$alias_record['#name']] = $alias_record;
  717. }
  718. }
  719. return $result_list;
  720. }
  721. /**
  722. * Check to see if the uri is the same in the source and target
  723. * lists for all items in the array. This is a strong requirement
  724. * in D6; in D7, it is still highly convenient for the uri to
  725. * be the same, because the site folder name == the uri, and if
  726. * the uris match, then it is easier to rsync between remote machines.
  727. *
  728. * @param $source
  729. * Array of source alias records
  730. * @param $target
  731. * Array of target alias records to compare against source list
  732. * @return
  733. * TRUE iff the uris of the sources and targets are in alignment
  734. */
  735. function drush_sitealias_check_lists_alignment($source, $target) {
  736. $is_aligned = TRUE;
  737. $i = 0;
  738. foreach ($source as $one_source) {
  739. if ((!isset($target[$i])) || (!_drush_sitelist_check_site_records($one_source, $target[$i]))) {
  740. $is_aligned = FALSE;
  741. break;
  742. }
  743. ++$i;
  744. }
  745. return $is_aligned;
  746. }
  747. /**
  748. * If the source and target lists contain alias records to the same
  749. * sets of sites, but in different orders, this routine will re-order
  750. * the lists so that they are in alignment.
  751. *
  752. * TODO: Review the advisability of this operation.
  753. */
  754. function drush_sitelist_align_lists(&$source, &$target, &$source_result, &$target_result) {
  755. $source_result = array();
  756. $target_result = array();
  757. foreach ($source as $key => $one_source) {
  758. $one_target = _drush_sitelist_find_in_list($one_source, $target);
  759. if ($one_target !== FALSE) {
  760. $source_result[] = $one_source;
  761. $target_result[] = $one_target;
  762. unset($source[$key]);
  763. }
  764. }
  765. $source = $source_result;
  766. $target = $target_result;
  767. }
  768. function _drush_sitelist_find_in_list($one_source, &$target) {
  769. $result = FALSE;
  770. foreach ($target as $key => $one_target) {
  771. if(_drush_sitelist_check_site_records($one_source, $one_target)) {
  772. $result = $one_target;
  773. unset($target[$key]);
  774. }
  775. }
  776. return $result;
  777. }
  778. function _drush_sitelist_check_site_records($source, $target) {
  779. if ((array_key_exists('uri', $source)) && (array_key_exists('uri', $target)) && ($source['uri'] == $target['uri'])) {
  780. return TRUE;
  781. }
  782. return FALSE;
  783. }
  784. /**
  785. * Initialize an alias record; called as soon as the alias
  786. * record is loaded from its alias file, before it is stored
  787. * in the cache.
  788. *
  789. * @param alias_record
  790. * The alias record to be initialized; paramter is modified in place.
  791. */
  792. function _drush_sitealias_initialize_alias_record(&$alias_record) {
  793. // If there is a 'from-list' entry, then build a derived
  794. // list based on the site list with the given name.
  795. if (array_key_exists('from-list', $alias_record)) {
  796. // danger of infinite loops... move to transient defaults?
  797. $from_record = drush_sitealias_get_record($alias_record['from-list']);
  798. $from_list = drush_sitealias_resolve_sitelist($from_record);
  799. $derived_list = array();
  800. foreach ($from_list as $one_record) {
  801. $derived_record = _drush_sitealias_derive_record($one_record, $alias_record);
  802. $derived_list[] = drush_sitealias_alias_record_to_spec($derived_record);
  803. }
  804. $alias_record = array();
  805. if (!empty($derived_list)) {
  806. $alias_record['site-list'] = $derived_list;
  807. }
  808. }
  809. // If there is a 'site-search-path' entry, then build
  810. // a 'site-list' entry from all of the sites that can be
  811. // found in the search path.
  812. if (array_key_exists('site-search-path', $alias_record)) {
  813. // TODO: Is there any point in merging the sites from
  814. // the search path with any sites already listed in the
  815. // 'site-list' entry? For now we'll just overwrite.
  816. $search_path = $alias_record['site-search-path'];
  817. if (!is_array($search_path)) {
  818. $search_path = explode(',', $search_path);
  819. }
  820. $found_sites = _drush_sitealias_find_local_sites($search_path);
  821. $alias_record['site-list'] = $found_sites;
  822. // The 'unordered-list' flag indicates that the order of the items in the site list is not stable.
  823. $alias_record['unordered-list'] = '1';
  824. // DEBUG: var_export($alias_record, FALSE);
  825. }
  826. if (array_key_exists('site-list', $alias_record)) {
  827. if (!is_array($alias_record['site-list'])) {
  828. $alias_record['site-list'] = explode(',', $alias_record['site-list']);
  829. }
  830. }
  831. }
  832. /**
  833. * Add "static" default values to the given alias record. The
  834. * difference between a static default and a transient default is
  835. * that static defaults -always- exist in the alias record, and
  836. * they are cached, whereas transient defaults are only added
  837. * if the given drush command explicitly adds them.
  838. *
  839. * @param alias_record
  840. * An alias record with most values already filled in
  841. */
  842. function _drush_sitealias_add_static_defaults(&$alias_record) {
  843. // If there is a 'db-url' entry but not 'databases' entry, then we will
  844. // build 'databases' from 'db-url' so that drush commands that use aliases
  845. // can always count on using a uniform 'databases' array.
  846. if (isset($alias_record['db-url']) && !isset($alias_record['databases'])) {
  847. $alias_record['databases'] = drush_sitealias_convert_db_from_db_url($alias_record['db-url']);
  848. }
  849. // Adjustments for aliases to drupal instances (as opposed to aliases that are site lists)
  850. if (array_key_exists('uri', $alias_record)) {
  851. // Make sure that there is always a 'path-aliases' array
  852. if (!array_key_exists('path-aliases', $alias_record)) {
  853. $alias_record['path-aliases'] = array();
  854. }
  855. // If there is a 'root' entry, then copy it to the '%root' path alias
  856. $alias_record['path-aliases']['%root'] = $alias_record['root'];
  857. }
  858. }
  859. function _drush_sitealias_derive_record($from_record, $modifying_record) {
  860. $result = $from_record;
  861. // If there is a 'remote-user' in the modifying record, copy it.
  862. if (array_key_exists('remote-user', $modifying_record)) {
  863. $result['remote-user'] = $from_record['remote_user'];
  864. }
  865. // If there is a 'remote-host', then:
  866. // If it is empty, clear the remote host in the result record
  867. // If it ends in '.', then prepend it to the remote host in the result record
  868. // Otherwise, copy it to the result record
  869. if (array_key_exists('remote-host', $modifying_record)) {
  870. $remote_host_modifier = $modifying_record['remote-host'];
  871. if(empty($remote_host_modifier)) {
  872. unset($result['remote-host']);
  873. unset($result['remote-user']);
  874. }
  875. elseif ($remote_host_modifier[strlen($remote_host_modifier)-1] == '.') {
  876. $result['remote-host'] = $remote_host_modifier . $result['remote-host'];
  877. }
  878. else {
  879. $result['remote-host'] = $remote_host_modifier;
  880. }
  881. }
  882. // If there is a 'root', then:
  883. // If it begins with '/', copy it to the result record
  884. // Otherwise, append it to the result record
  885. if (array_key_exists('root', $modifying_record)) {
  886. $root_modifier = $modifying_record['root'];
  887. if($root_modifier[0] == '/') {
  888. $result['root'] = $root_modifier;
  889. }
  890. else {
  891. $result['root'] = $result['root'] . '/' . $root_modifier;
  892. }
  893. }
  894. // Poor man's realpath: take out the /../ with preg_replace.
  895. // (realpath fails if the files in the path do not exist)
  896. while(strpos($result['root'], '/../') !== FALSE) {
  897. $result['root'] = preg_replace('/\w+\/\.\.\//', '', $result['root']);
  898. }
  899. // TODO: Should we allow the uri to be transformed?
  900. // I think that if the uri does not match, then you should
  901. // always build the list by hand, and not rely on '_drush_sitealias_derive_record'.
  902. return $result;
  903. }
  904. /**
  905. * Convert from an alias record to a site specification
  906. *
  907. * @param alias_record
  908. * The full alias record to convert
  909. *
  910. * @param with_db
  911. * True if the site specification should include a ?db-url term
  912. *
  913. * @return string
  914. * The site specification
  915. */
  916. function drush_sitealias_alias_record_to_spec($alias_record, $with_db = false) {
  917. $result = '';
  918. // TODO: we should handle 'site-list' records too.
  919. if (array_key_exists('site-list', $alias_record)) {
  920. // TODO: we should actually expand the site list and recompose it
  921. $result = implode(',', $alias_record['site-list']);
  922. }
  923. else {
  924. // There should always be a uri
  925. if (array_key_exists('uri', $alias_record)) {
  926. $result = '#' . drush_sitealias_uri_to_site_dir($alias_record['uri']);
  927. }
  928. // There should always be a root
  929. if (array_key_exists('root', $alias_record)) {
  930. $result = $alias_record['root'] . $result;
  931. }
  932. if (array_key_exists('remote-host', $alias_record)) {
  933. $result = $alias_record['remote-host'] . $result;
  934. if (array_key_exists('remote-user', $alias_record)) {
  935. $result = $alias_record['remote-user'] . '@' . $result;
  936. }
  937. }
  938. // Add the database info to the specification if desired
  939. if ($with_db) {
  940. // If db-url is not supplied, look it up from the remote
  941. // or local site and add it to the site alias
  942. if (!isset($alias_record['db-url'])) {
  943. drush_sitealias_add_db_url($alias_record);
  944. }
  945. $result = $result . '?db-url=' . urlencode(is_array($alias_record['db-url']) ? $alias_record['db-url']['default'] : $alias_record['db-url']);
  946. }
  947. }
  948. return $result;
  949. }
  950. /**
  951. * Search for drupal installations in the search path.
  952. *
  953. * @param search_path
  954. * An array of drupal root folders
  955. *
  956. * @return
  957. * An array of site specifications (/path/to/root#sitename.com)
  958. */
  959. function _drush_sitealias_find_local_sites($search_path) {
  960. $result = array();
  961. foreach ($search_path as $a_drupal_root) {
  962. $result = array_merge($result, _drush_find_local_sites_at_root($a_drupal_root));
  963. }
  964. return $result;
  965. }
  966. /**
  967. * Return a list of all of the local sites at the specified drupal root.
  968. */
  969. function _drush_find_local_sites_at_root($a_drupal_root = '', $search_depth = 1) {
  970. $site_list = array();
  971. $base_path = (empty($a_drupal_root) ? drush_get_context('DRUSH_DRUPAL_ROOT') : $a_drupal_root );
  972. if (!empty($base_path)) {
  973. if (drush_valid_drupal_root($base_path)) {
  974. // If $a_drupal_root is in fact a valid drupal root, then return
  975. // all of the sites found inside the 'sites' folder of this drupal instance.
  976. $site_list = _drush_find_local_sites_in_sites_folder($base_path);
  977. }
  978. else {
  979. $bootstrap_files = drush_scan_directory($base_path, '/' . basename(DRUSH_DRUPAL_BOOTSTRAP) . '/' , array('.', '..', 'CVS'), 0, drush_get_option('search-depth', $search_depth) + 1, 'filename', 1);
  980. foreach ($bootstrap_files as $one_bootstrap => $info) {
  981. $includes_dir = dirname($one_bootstrap);
  982. if (basename($includes_dir) == basename(dirname(DRUSH_DRUPAL_BOOTSTRAP))) {
  983. $drupal_root = dirname($includes_dir);
  984. $site_list = array_merge(_drush_find_local_sites_in_sites_folder($drupal_root), $site_list);
  985. }
  986. }
  987. }
  988. }
  989. return $site_list;
  990. }
  991. /**
  992. * Return a list of all of the local sites at the specified 'sites' folder.
  993. */
  994. function _drush_find_local_sites_in_sites_folder($a_drupal_root) {
  995. $site_list = array();
  996. // If anyone searches for sites at a given root, then
  997. // make sure that alias files stored at this root
  998. // directory are included in the alias search path
  999. drush_sitealias_add_to_alias_path($a_drupal_root);
  1000. $base_path = $a_drupal_root . '/sites';
  1001. // TODO: build a cache keyed off of $base_path (realpath($base_path)?),
  1002. // so that it is guarenteed that the lists returned will definitely be
  1003. // exactly the same should this routine be called twice with the same path.
  1004. $files = drush_scan_directory($base_path, '/settings\.php/', array('.', '..', 'CVS', 'all'), 0, 1, 'filename', 1);
  1005. foreach ($files as $filename => $info) {
  1006. if ($info->basename == 'settings.php') {
  1007. // First we'll resolve the realpath of the settings.php file,
  1008. // so that we get the correct drupal root when symlinks are in use.
  1009. $real_sitedir = dirname(realpath($filename));
  1010. $real_root = drush_locate_root($filename);
  1011. if ($real_root !== FALSE) {
  1012. $a_drupal_site = $real_root . '#' . basename($real_sitedir);
  1013. }
  1014. // If the symlink points to some folder outside of any drupal
  1015. // root, then we'll use the
  1016. else {
  1017. $uri = drush_sitealias_site_dir_from_filename($filename);
  1018. $a_drupal_site = $a_drupal_root . '#' . $uri;
  1019. }
  1020. // Add the site if it isn't already in the array
  1021. if (!in_array($a_drupal_site, $site_list)) {
  1022. $site_list[] = $a_drupal_site;
  1023. }
  1024. }
  1025. }
  1026. return $site_list;
  1027. }
  1028. function drush_sitealias_create_sites_alias($a_drupal_root = '') {
  1029. $sites_list = _drush_find_local_sites_at_root($a_drupal_root);
  1030. _drush_sitealias_cache_alias('sites', array('site-list' => $sites_list));
  1031. }
  1032. /**
  1033. * Add "transient" default values to the given alias record. The
  1034. * difference between a static default and a transient default is
  1035. * that static defaults -always- exist in the alias record,
  1036. * whereas transient defaults are only added if the given drush
  1037. * command explicitly calls this function. The other advantage
  1038. * of transient defaults is that it is possible to differentiate
  1039. * between a default value and an unspecified value, since the
  1040. * transient defaults are not added until requested.
  1041. *
  1042. * Since transient defaults are not cached, you should avoid doing
  1043. * expensive operations here. To be safe, drush commands should
  1044. * avoid calling this function more than once.
  1045. *
  1046. * @param alias_record
  1047. * An alias record with most values already filled in
  1048. */
  1049. function _drush_sitealias_add_transient_defaults(&$alias_record) {
  1050. if (isset($alias_record['path-aliases'])) {
  1051. // Add the path to the drush folder to the path aliases as !drush
  1052. if (!array_key_exists('%drush', $alias_record['path-aliases'])) {
  1053. if (array_key_exists('%drush-script', $alias_record['path-aliases'])) {
  1054. $alias_record['path-aliases']['%drush'] = dirname($alias_record['path-aliases']['%drush-script']);
  1055. }
  1056. else {
  1057. $alias_record['path-aliases']['%drush'] = dirname($GLOBALS['argv'][0]);
  1058. }
  1059. }
  1060. // Add the path to the site folder to the path aliases as !site
  1061. if (!array_key_exists('%site', $alias_record['path-aliases']) && array_key_exists('uri', $alias_record)) {
  1062. $alias_record['path-aliases']['%site'] = 'sites/' . drush_sitealias_uri_to_site_dir($alias_record['uri']) . '/';
  1063. }
  1064. }
  1065. }
  1066. /**
  1067. * Find the name of a local alias record that has the specified
  1068. * root and uri.
  1069. */
  1070. function _drush_sitealias_find_local_alias_name($root, $uri) {
  1071. $result = '';
  1072. $all_site_aliases =& drush_get_context('site-aliases');
  1073. foreach ($all_site_aliases as $alias_name => $alias_values) {
  1074. if (!array_key_exists('remote-host', $alias_values) && array_key_exists('root', $alias_values) && array_key_exists('uri', $alias_values) && ($alias_name != '@self')) {
  1075. if (($root == $alias_values['root']) && ($uri == $alias_values['uri'])) {
  1076. $result = $alias_name;
  1077. }
  1078. }
  1079. }
  1080. return $result;
  1081. }
  1082. /**
  1083. * If '$alias' is the name of a folder in the sites folder of the given drupal
  1084. * root, then build an alias record for it
  1085. *
  1086. * @param alias
  1087. * The name of the site in the 'sites' folder to convert
  1088. * @return array
  1089. * An alias record, or empty if none found.
  1090. */
  1091. function _drush_sitealias_find_record_for_local_site($alias, $drupal_root = null) {
  1092. $alias_record = array();
  1093. // Clip off the leading '#' if it is there
  1094. if (substr($alias,0,1) == '#') {
  1095. $alias = substr($alias,1);
  1096. }
  1097. if (!isset($drupal_root)) {
  1098. //$drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  1099. $drupal_root = drush_get_option(array('r', 'root'), drush_locate_root());
  1100. }
  1101. if (isset($drupal_root)) {
  1102. $alias_dir = drush_sitealias_uri_to_site_dir($alias);
  1103. $site_settings_file = $drupal_root . '/sites/' . $alias_dir . '/settings.php';
  1104. $alias_record = drush_sitealias_build_record_from_settings_file($site_settings_file, $alias, $drupal_root);
  1105. }
  1106. return $alias_record;
  1107. }
  1108. function drush_sitealias_build_record_from_settings_file($site_settings_file, $alias = null, $drupal_root = null) {
  1109. $alias_record = array();
  1110. if (file_exists($site_settings_file)) {
  1111. if (!isset($drupal_root)) {
  1112. $drupal_root = drush_locate_root($site_settings_file);
  1113. }
  1114. $alias_record['root'] = $drupal_root;
  1115. if (isset($alias)) {
  1116. $alias_record['uri'] = $alias;
  1117. }
  1118. else {
  1119. $alias_record['uri'] = _drush_sitealias_site_dir_to_uri(drush_sitealias_site_dir_from_filename($site_settings_file));
  1120. }
  1121. }
  1122. return $alias_record;
  1123. }
  1124. /**
  1125. * Pull the site directory from the path to settings.php
  1126. *
  1127. * @param site_settings_file
  1128. * path to settings.php
  1129. *
  1130. * @return string
  1131. * the site directory component of the path to settings.php
  1132. */
  1133. function drush_sitealias_site_dir_from_filename($site_settings_file) {
  1134. return basename(dirname($site_settings_file));
  1135. }
  1136. /**
  1137. * Convert from a URI to a site directory.
  1138. *
  1139. * @param uri
  1140. * A uri, such as http://domain.com:8080/drupal
  1141. * @return string
  1142. * A directory, such as domain.com.8080.drupal
  1143. */
  1144. function drush_sitealias_uri_to_site_dir($uri) {
  1145. return str_replace(array('http://', '/', ':'), array('', '.', '.'), $uri);
  1146. }
  1147. /**
  1148. * Convert from an old-style database URL to an array of database settings
  1149. *
  1150. * @param db_url
  1151. * A Drupal 6 db-url string to convert, or an array with a 'default' element.
  1152. * @return array
  1153. * An array of database values containing only the 'default' element of
  1154. * the db_url.
  1155. */
  1156. function drush_convert_db_from_db_url($db_url) {
  1157. if (is_array($db_url)) {
  1158. $url = parse_url($db_url['default']);
  1159. }
  1160. else {
  1161. $url = parse_url($db_url);
  1162. }
  1163. // Fill in defaults to prevent notices.
  1164. $url += array(
  1165. 'driver' => NULL,
  1166. 'user' => NULL,
  1167. 'pass' => NULL,
  1168. 'host' => NULL,
  1169. 'port' => NULL,
  1170. 'database' => NULL,
  1171. );
  1172. $url = (object)array_map('urldecode', $url);
  1173. return array(
  1174. 'driver' => $url->scheme == 'mysqli' ? 'mysql' : $url->scheme,
  1175. 'username' => $url->user,
  1176. 'password' => $url->pass,
  1177. 'port' => $url->port,
  1178. 'host' => $url->host,
  1179. // Remove leading / character from database names, unless we're installing
  1180. // to SQLite (which won't have a slash there unless it's part of a path).
  1181. 'database' => $url->scheme === 'sqlite' ? $url->path : substr($url->path, 1)
  1182. );
  1183. }
  1184. /**
  1185. * Convert from an old-style database URL to an array of database settings
  1186. *
  1187. * @param db_url
  1188. * A Drupal 6 db-url string to convert, or an array with multiple db-urls.
  1189. * @return array
  1190. * An array of database values.
  1191. */
  1192. function drush_sitealias_convert_db_from_db_url($db_url) {
  1193. $result = array();
  1194. if (!is_array($db_url)) {
  1195. $result = array('default' => array('default' => drush_convert_db_from_db_url($db_url)));
  1196. }
  1197. else {
  1198. foreach ($db_url as $one_name => $one_db_url) {
  1199. $result[$one_name] = array('default' => drush_convert_db_from_db_url($one_db_url));
  1200. }
  1201. }
  1202. return $result;
  1203. }
  1204. /**
  1205. * Utility function used by drush_get_alias; keys that start with
  1206. * '%' or '!' are path aliases, the rest are entries in the alias record.
  1207. */
  1208. function _drush_sitealias_set_record_element(&$alias_record, $key, $value) {
  1209. if ((substr($key,0,1) == '%') || (substr($key,0,1) == '!')) {
  1210. $alias_record['path-aliases'][$key] = $value;
  1211. }
  1212. elseif (!empty($key)) {
  1213. $alias_record[$key] = $value;
  1214. }
  1215. }
  1216. /**
  1217. * Looks up the specified alias record and calls through to
  1218. * drush_sitealias_set_alias_context, below.
  1219. *
  1220. * @param alias
  1221. * The name of the alias record
  1222. * @param prefix
  1223. * The prefix value to afix to the beginning of every
  1224. * key set.
  1225. * @return boolean
  1226. * TRUE is an alias was found and processed.
  1227. */
  1228. function _drush_sitealias_set_context_by_name($alias, $prefix = '') {
  1229. $site_alias_settings = drush_sitealias_get_record($alias);
  1230. if (!empty($site_alias_settings)) {
  1231. // Create an alias '@self'
  1232. _drush_sitealias_cache_alias('self', $site_alias_settings);
  1233. drush_sitealias_set_alias_context($site_alias_settings, $prefix);
  1234. return TRUE;
  1235. }
  1236. return FALSE;
  1237. }
  1238. /**
  1239. * Given a site alias record, copy selected fields from it
  1240. * into the drush 'alias' context. The 'alias' context has
  1241. * lower precedence than the 'cli' context, so values
  1242. * set by an alias record can be overridden by command-line
  1243. * parameters.
  1244. *
  1245. * @param site_alias_settings
  1246. * An alias record
  1247. * @param prefix
  1248. * The prefix value to afix to the beginning of every
  1249. * key set. For example, if this function is called once with
  1250. * 'source-' and again with 'destination-' prefixes, then the
  1251. * source database records will be stored in 'source-databases',
  1252. * and the destination database records will be in
  1253. * 'destination-databases'.
  1254. */
  1255. function drush_sitealias_set_alias_context($site_alias_settings, $prefix = '') {
  1256. $options = drush_get_context('alias');
  1257. // There are some items that we should just skip
  1258. $skip_list = drush_get_special_keys();
  1259. // Also skip 'remote-host' and 'remote-user' if 'remote-host' is actually
  1260. // the local machine
  1261. if (array_key_exists('remote-host', $site_alias_settings) && drush_is_local_host($site_alias_settings['remote-host'])) {
  1262. $skip_list[] = 'remote-host';
  1263. $skip_list[] = 'remote-user';
  1264. }
  1265. // If prefix is set, then copy from the 'prefix-' version
  1266. // of the drush special keys ('command-specific', 'path-aliases')
  1267. // into the ordinary version. This will allow us to set
  1268. // 'source-command-specific' options that will only apply when
  1269. // the alias is used as the source option for rsync or sql-sync.
  1270. if (!empty($prefix)) {
  1271. $special_contexts = drush_get_special_keys();
  1272. foreach ($special_contexts as $option_name) {
  1273. if (array_key_exists($prefix . $option_name, $site_alias_settings)) {
  1274. $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];
  1275. }
  1276. }
  1277. }
  1278. // Transfer all options from the site alias to the drush options
  1279. // in the 'alias' context.
  1280. foreach ($site_alias_settings as $key => $value) {
  1281. // Special handling for path aliases:
  1282. if ($key == "path-aliases") {
  1283. $path_aliases = $value;
  1284. foreach (array('%drush-script', '%dump', '%dump-dir', '%include') as $path_key) {
  1285. if (array_key_exists($path_key, $path_aliases)) {
  1286. // Evaluate the path value, and substitute any path references found.
  1287. // ex: '%dump-dir' => '%root/dumps' will store sql-dumps in the folder
  1288. // 'dumps' in the Drupal root folder for the site.
  1289. $evaluated_path = str_replace(array_keys($path_aliases), array_values($path_aliases), $path_aliases[$path_key]);
  1290. $options[$prefix . substr($path_key, 1)] = $evaluated_path;
  1291. }
  1292. }
  1293. }
  1294. // Special handling for command-specific
  1295. elseif ($key == "command-specific") {
  1296. $options[$key] = $value;
  1297. }
  1298. elseif (!in_array($key, $skip_list)) {
  1299. $options[$prefix . $key] = $value;
  1300. }
  1301. }
  1302. drush_set_config_options('alias', $options);
  1303. }
  1304. /**
  1305. * Call prior to drush_sitealias_evaluate_path to insure
  1306. * that any site-specific aliases associated with any
  1307. * local site in $path are defined.
  1308. */
  1309. function _drush_sitealias_preflight_path($path) {
  1310. $alias = NULL;
  1311. // Parse site aliases if there is a colon in the path
  1312. $colon_pos = strpos($path, ':');
  1313. if ($colon_pos !== FALSE) {
  1314. $alias = substr($path, 0, $colon_pos);
  1315. $path = substr($path, $colon_pos + 1);
  1316. $site_alias_settings = _drush_sitealias_get_record($alias);
  1317. if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
  1318. return NULL;
  1319. }
  1320. $machine = $alias;
  1321. }
  1322. else {
  1323. $machine = '';
  1324. // if the path is a site alias or a local site...
  1325. $site_alias_settings = _drush_sitealias_get_record($path);
  1326. if (empty($site_alias_settings) && (substr($path,0,1) == '@')) {
  1327. return NULL;
  1328. }
  1329. if (!empty($site_alias_settings) || drush_is_local_host($path)) {
  1330. $alias = $path;
  1331. $path = '';
  1332. }
  1333. }
  1334. return array('alias' => $alias, 'path' => $path, 'machine' => $machine);
  1335. }
  1336. /**
  1337. * Evaluate a path from its shorthand form to a literal path
  1338. * usable by rsync.
  1339. *
  1340. * A path is "machine:/path" or "machine:path" or "/path" or "path".
  1341. * 'machine' might instead be an alias record, or the name
  1342. * of a site in the 'sites' folder. 'path' might be (or contain)
  1343. * '%root' or some other path alias. This function will examine
  1344. * all components of the path and evaluate them as necessary to
  1345. * come to the final path.
  1346. *
  1347. * @param path
  1348. * The path to evaluate
  1349. * @param additional_options
  1350. * An array of options that overrides whatever was passed in on
  1351. * the command line (like the 'process' context, but only for
  1352. * the scope of this one call).
  1353. * @return
  1354. * The site record for the machine specified in the path, if any,
  1355. * with the path to pass to rsync (including the machine specifier)
  1356. * in the 'evaluated-path' item.
  1357. */
  1358. function drush_sitealias_evaluate_path($path, &$additional_options, $local_only = FALSE) {
  1359. $site_alias_settings = array();
  1360. $path_aliases = array();
  1361. $remote_user = '';
  1362. $preflight = _drush_sitealias_preflight_path($path);
  1363. if (!isset($preflight)) {
  1364. return NULL;
  1365. }
  1366. $alias = $preflight['alias'];
  1367. $path = $preflight['path'];
  1368. $machine = $preflight['machine'];
  1369. if (isset($alias)) {
  1370. $site_alias_settings = drush_sitealias_get_record($alias);
  1371. }
  1372. if (!empty($site_alias_settings)) {
  1373. if ($local_only && array_key_exists('remote-host', $site_alias_settings)) {
  1374. 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."));
  1375. }
  1376. // Apply any options from this alias that might affect our rsync
  1377. drush_sitealias_set_alias_context($site_alias_settings);
  1378. // Use 'remote-host' from settings if available; otherwise site is local
  1379. if (array_key_exists('remote-host', $site_alias_settings) && !drush_is_local_host($site_alias_settings['remote-host'])) {
  1380. if (array_key_exists('remote-user', $site_alias_settings)) {
  1381. $remote_user = $site_alias_settings['remote-user'] . '@';
  1382. }
  1383. $machine = $remote_user . $site_alias_settings['remote-host'];
  1384. }
  1385. else {
  1386. $machine = '';
  1387. }
  1388. }
  1389. else {
  1390. // Strip the machine portion of the path if the
  1391. // alias points to the local machine.
  1392. if (drush_is_local_host($machine)) {
  1393. $machine = '';
  1394. }
  1395. else {
  1396. $machine = "$remote_user$machine";
  1397. }
  1398. }
  1399. // If the --exclude-other-sites option is specified, then
  1400. // convert that into --include-path='%site' and --exclude-sites.
  1401. if (drush_get_option_override($additional_options, 'exclude-other-sites', FALSE) && !drush_get_option_override($additional_options, 'exclude-other-sites-processed', FALSE, 'process')) {
  1402. $additional_options['include-path'] = '%site,' . drush_get_option_override($additional_options, 'include-path', '');
  1403. $additional_options['exclude-sites'] = TRUE;
  1404. $additional_options['exclude-other-sites-processed'] = TRUE;
  1405. }
  1406. // If the --exclude-files option is specified, then
  1407. // convert that into --exclude-path='%files'.
  1408. if (drush_get_option_override($additional_options, 'exclude-files', FALSE) && !drush_get_option_override($additional_options, 'exclude-files-processed', FALSE, 'process')) {
  1409. $additional_options['exclude-path'] = '%files,' . drush_get_option_override($additional_options, 'exclude-path', '');
  1410. $additional_options['exclude-files-processed'] = TRUE;
  1411. }
  1412. // If there was no site specification given, and the
  1413. // machine is local, then try to look
  1414. // up an alias record for the default drush site.
  1415. if (empty($site_alias_settings) && empty($machine)) {
  1416. $drush_uri = drush_bootstrap_value('drush_uri', drush_get_option(array('l', 'uri'), 'default'));
  1417. $site_alias_settings = drush_sitealias_get_record($drush_uri);
  1418. }
  1419. // Always add transient defaults
  1420. _drush_sitealias_add_transient_defaults($site_alias_settings);
  1421. // The $resolve_path variable is used by drush_sitealias_resolve_path_references
  1422. // to test to see if there are any path references such as %site or %files
  1423. // in it, so that resolution is only done if the path alias is referenced.
  1424. // Therefore, we can concatenate without worrying too much about the structure of
  1425. // this variable's contents.
  1426. $include_path = drush_get_option_override($additional_options, 'include-path', '');
  1427. $exclude_path = drush_get_option_override($additional_options, 'exclude-path', '');
  1428. $resolve_path = $path . $include_path . $exclude_path;
  1429. // Resolve path aliases such as %files, if any exist in the path
  1430. if (!empty($resolve_path)) {
  1431. drush_sitealias_resolve_path_references($site_alias_settings, $resolve_path);
  1432. }
  1433. if (array_key_exists('path-aliases', $site_alias_settings)) {
  1434. $path_aliases = $site_alias_settings['path-aliases'];
  1435. }
  1436. // Get the 'root' setting from the alias; if it does not
  1437. // exist, then get the root from the bootstrapped site.
  1438. if (array_key_exists('root', $site_alias_settings)) {
  1439. $drupal_root = $site_alias_settings['root'];
  1440. }
  1441. else {
  1442. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_SITE);
  1443. $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  1444. }
  1445. if (empty($drupal_root)) {
  1446. $drupal_root = '';
  1447. }
  1448. // Add a slash to the end of the drupal root, as below.
  1449. elseif ($drupal_root[strlen($drupal_root)-1] != '/') {
  1450. $drupal_root = $drupal_root . '/';
  1451. }
  1452. $full_path_aliases = $path_aliases;
  1453. foreach ($full_path_aliases as $key => $value) {
  1454. // Expand all relative path aliases to be based off of the Drupal root
  1455. if ((substr($value, 0, 1) != '/') && ($key != '%root')) {
  1456. $full_path_aliases[$key] = $drupal_root . $value;
  1457. }
  1458. // We do not want slashes on the end of our path aliases.
  1459. if (substr($value, 0, -1) == '/') {
  1460. $full_path_aliases[$key] = substr($full_path_aliases[$key], -1);
  1461. }
  1462. }
  1463. // Fill in path aliases in the path, the include path and the exclude path.
  1464. $path = str_replace(array_keys($full_path_aliases), array_values($full_path_aliases), $path);
  1465. if (!empty($include_path)) {
  1466. drush_set_option('include-path', str_replace(array_keys($path_aliases), array_values($path_aliases), $include_path));
  1467. }
  1468. if (!empty($exclude_path)) {
  1469. drush_set_option('exclude-path', str_replace(array_keys($path_aliases), array_values($path_aliases), $exclude_path));
  1470. }
  1471. // The path component is just the path part of the full
  1472. // machine:path specification (including the colon).
  1473. $path_component = (!empty($path) ? ':' . $path : '');
  1474. // Next make the rsync path, which includes the machine
  1475. // and path components together.
  1476. // First make empty paths or relative paths start from the drupal root.
  1477. if (empty($path) || ($path[0] != '/')) {
  1478. $path = $drupal_root . $path;
  1479. }
  1480. // If there is a $machine component, to the path, then
  1481. // add it to the beginning
  1482. $evaluated_path = $path;
  1483. if (!empty($machine)) {
  1484. $evaluated_path = $machine . ':' . $path;
  1485. }
  1486. //
  1487. // Add our result paths:
  1488. //
  1489. // evaluated-path: machine:/path
  1490. // server-component: machine
  1491. // path-component: :/path
  1492. // path: /path
  1493. // user-path: path (as specified in input parameter)
  1494. //
  1495. $site_alias_settings['evaluated-path'] = $evaluated_path;
  1496. if (!empty($machine)) {
  1497. $site_alias_settings['server-component'] = $machine;
  1498. }
  1499. $site_alias_settings['path-component'] = $path_component;
  1500. $site_alias_settings['path'] = $path;
  1501. $site_alias_settings['user-path'] = $preflight['path'];
  1502. return $site_alias_settings;
  1503. }
  1504. /**
  1505. * Option keys used for site selection.
  1506. */
  1507. function drush_sitealias_site_selection_keys() {
  1508. return array('remote-host', 'remote-user', 'ssh-options', '#name');
  1509. }
  1510. function sitealias_find_local_drupal_root($site_list) {
  1511. $drupal_root = NULL;
  1512. foreach ($site_list as $site) {
  1513. if (($drupal_root == NULL) && (array_key_exists('root', $site) && !array_key_exists('remote-host', $site))) {
  1514. $drupal_root = $site['root'];
  1515. }
  1516. }
  1517. return $drupal_root;
  1518. }
  1519. /**
  1520. * Helper function to obtain the keys' names that need special handling in certain
  1521. * cases.
  1522. * @return
  1523. * A non-associative array containing the needed keys' names.
  1524. */
  1525. function drush_get_special_keys() {
  1526. $special_keys = array(
  1527. 'command-specific',
  1528. 'site-aliases',
  1529. );
  1530. return $special_keys;
  1531. }