provision_drupal.drush.inc 28.6 KB
Newer Older
1 2 3 4 5 6
<?php
/**
 * @file
 * Drupal specific functions for the provisioning framework.
 *
 * This module is responsible for the creation and maintenance of the drupal settings.php file, the sites directory structure
7
 * and all the install api code.
8 9
 */

10 11
function provision_drupal_drush_help($section) {
  switch ($section) {
12
    case 'error:PROVISION_CONFIG_NOT_VALID' :
13
      return dt('Config file could not be loaded.');
14
    case 'error:PROVISION_DRUPAL_SITE_INSTALLED' :
15
      return dt('Site has already been installed.');
16
    case 'error:PROVISION_DRUPAL_SITE_NOT_FOUND' :
17
      return dt('Site was not found.');
18
    case 'error:PROVISION_DRUPAL_INSTALL_FAILED' :
19
      return dt('Could not complete Drupal installation.');
20
    case 'error:PROVISION_DRUPAL_UPDATE_FAILED' :
21 22 23 24 25
      return dt('Could not complete Drupal update.');
    case 'error:PROVISION_BACKUP_PATH_NOT_FOUND' :
      return dt("Backup directory does not exist.");
    case 'error:PROVISION_DRUPAL_INSTALL_MISSING_REQUIREMENTS' :
      return dt("Could not meet the requirements for installing the drupal profile");
26
    case 'error:PROVISION_REQUIRES_URL' :
27 28 29 30 31
      return dt('You need to specify the URL argument for this command');
  }

}

32 33 34
function provision_drupal_drush_engine_drupal() {
  $engines = array();
  $engines['clear'] = array();
35 36 37 38
  $engines['cron_key'] = array();
  $engines['deploy'] = array();
  $engines['import'] = array();
  $engines['install'] = array();
39 40 41 42 43
  $engines['packages'] = array();
  $engines['verify'] = array();
  return $engines;
}

44 45 46 47 48 49 50 51

/**
 * Finalize the platform / site
 *
 * This will run only if there were no errors in any of the previous hooks,
 * and will allow us to cache the successful settings to the site.php/ drushrc.php
 * files for future runs.
 */
52
function provision_drupal_drush_exit() {
53 54
  $command = drush_get_command();
  $command = explode(" ", $command['command']);
55

56
  if (preg_match("/^provision-/", $command[0]) && drush_get_option('provision_save_config', TRUE)) {
drumm's avatar
drumm committed
57
    if (d()->type === 'site') {
58
      if (drush_get_option('installed')) {
59 60
        // Don't generate the drushrc.php on provision-save/delete commands.
        if (!preg_match("/^provision-(save|delete)/", $command[0])) {
Adrian Rossouw's avatar
Adrian Rossouw committed
61 62
          provision_save_site_data();
        }
63 64
      }
    }
drumm's avatar
drumm committed
65
    elseif (d()->type === 'platform') {
66 67
      // Don't generate the drushrc.php on provision-save/delete commands.
      if (!preg_match("/^provision-(save|delete)/", $command[0])) {
68
        provision_save_platform_data();
69
      }
Adrian Rossouw's avatar
Adrian Rossouw committed
70
    }
71 72 73
  }
}

74 75 76 77 78 79
/**
 * Test to see if the site settings.php exists
 *
 * @return
 *   If the file exists, return TRUE, else return FALSE.
 */
80
function _provision_drupal_site_exists() {
81
  return is_readable(d()->site_path . '/settings.php');
82 83
}

84 85 86 87 88 89 90 91
/**
 * This command does the actual installation in it's own thread,
 * so we can recover gracefully if things go really wrong.
 */
function drush_provision_drupal_provision_install_backend() {
  drush_include_engine('drupal', 'install');
}

drumm's avatar
drumm committed
92 93 94
/**
 * Sync the current Drupal platform and, if applicable, site. Call after
 * finishing operations that affect the filesystem.
95 96 97 98
 *
 * @param boolean $override_slave_authority
 *    Overwrite e.g. the files directory on the slave.
 *    Useful when the master server has donw file operations, such as restoring a backup.
drumm's avatar
drumm committed
99
 */
100
function provision_drupal_push_site($override_slave_authority = FALSE) {
101

102
  provision_file()->create_dir(d()->server->http_platforms_path, dt("Platforms"), 0755);
103 104 105
  d()->server->sync(d()->server->http_platforms_path, array(
    'exclude' => d()->server->http_platforms_path . '/*',  // Make sure remote directory is created
  ));
106 107

  // Sync the platform
drumm's avatar
drumm committed
108
  d()->service('http')->sync(d()->root, array('exclude-sites' => TRUE));
109

110
  if (d()->type === 'site') {
111 112 113
    // Check whether we're hosted on a cluster, in which case, the master is
    // authoritative.
    $cluster = d()->platform->web_server->http_service_type == 'cluster' ? TRUE : FALSE;
114
    $options = array();
115
    if ($override_slave_authority || $cluster) {
116 117 118 119 120 121 122 123 124 125 126 127 128
      $exclude = NULL;
    }
    else {
      $exclude = 'files/*' . PATH_SEPARATOR . 'private/*';
    }

    // Store the current exclude-path option
    $old_exclude = NULL;
    if(!is_null($exclude)) {
      $old_exclude = drush_get_option('exclude-paths');
      drush_set_option('exclude-paths', $exclude);
    }

129
    // Sync all filesystem changes to the remote server.
130 131 132 133 134 135 136 137 138 139
    d()->service('http')->sync(d()->site_path, $options);

    // Reset the exclude-path option
    if(!is_null($exclude)) {
      if(empty($old_exclude)) {
        drush_unset_option('exclude-paths');
      } else {
        drush_set_option('exclude-paths', $old_exclude);
      }
    }
140 141 142
  }
}

143 144 145 146 147
/**
 * Sync the current Drupal site BACK from a slave. Call before
 * running operations that need files where the slave is authoritative.
 *
 * E.g. before a backup is made.
148 149
 *
 * @param string $alias The site alias to work on, defaults to the current site.
150
 */
151
function provision_drupal_fetch_site($alias = NULL) {
152

153
  $site = d($alias);
154
  // synch filesystem changes back from the remote server.
155 156 157 158 159
  $site->service('http')->fetch($site->site_path . '/files/');
  $site->service('http')->fetch($site->site_path . '/private/');
  $site->service('http')->fetch($site->site_path . '/modules/');
  $site->service('http')->fetch($site->site_path . '/themes/');
  $site->service('http')->fetch($site->site_path . '/libraries/');
160
  // Questionable... who is authoritive?
161
  $site->service('http')->fetch($site->site_path . '/local.settings.php');
162 163
}

164 165 166
/**
 * Generate a settings file for the site.
 */
167
function _provision_drupal_create_settings_file() {
168
  $config = new Provision_Config_Drupal_Settings(d()->name, drush_get_context('site'));
169
  $config->write();
170 171 172 173
}

/**
 * Create the directories needed to host a drupal site
174
 *
175
 * Also maintains permissions on existing directories.
176 177 178 179 180
 *
 * @param $url
 *   The url of the site being invoked.
  */
function _provision_drupal_create_directories($url = NULL) {
181
  if (is_null($url)) {
182 183 184 185 186 187
    if (d()->type == 'site') {
      $url = d()->uri;
    }
    else {
      $url = 'all';
    }
188
  }
189 190
  # those directories will be created and their modes changed
  $mkdir = array(
191
    "sites/$url"                 => 0755,
192
    # those should be writable by the aegir primary group to ease development
193
    "sites/$url/themes"          => 02775,
194
    "sites/$url/modules"         => 02775,
195
    "sites/$url/libraries"       => 02775, # http://drupal.org/node/496240
196
  );
Adrian Rossouw's avatar
Adrian Rossouw committed
197

198
  $chgrp = array();
199
  // special case: platform. do not handle files dir
200
  if ($url != 'all') {
201 202
    $mkdir["sites/$url/files"]            = 02770;
    $chgrp["sites/$url/files"]            = d('@server_master')->web_group;
203

204 205
    $mkdir["sites/$url/files/tmp"]        = 02770;
    $chgrp["sites/$url/files/tmp"]        = d('@server_master')->web_group;
206

207 208
    $mkdir["sites/$url/files/images"]     = 02770;
    $chgrp["sites/$url/files/images"]     = d('@server_master')->web_group;
209

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    $mkdir["sites/$url/files/pictures"]   = 02770;
    $chgrp["sites/$url/files/pictures"]   = d('@server_master')->web_group;

    $mkdir["sites/$url/files/css"]        = 02770;
    $chgrp["sites/$url/files/css"]        = d('@server_master')->web_group;

    $mkdir["sites/$url/files/js"]         = 02770;
    $chgrp["sites/$url/files/js"]         = d('@server_master')->web_group;

    $mkdir["sites/$url/files/ctools"]     = 02770;
    $chgrp["sites/$url/files/ctools"]     = d('@server_master')->web_group;

    $mkdir["sites/$url/files/imagecache"] = 02770;
    $chgrp["sites/$url/files/imagecache"] = d('@server_master')->web_group;

    $mkdir["sites/$url/files/locations"]  = 02770;
    $chgrp["sites/$url/files/locations"]  = d('@server_master')->web_group;
227 228

    // d7 support
229 230
    $mkdir["sites/$url/private"]          = 02770;
    $chgrp["sites/$url/private"]          = d('@server_master')->web_group;
231

232 233
    $mkdir["sites/$url/private/files"]    = 02770;
    $chgrp["sites/$url/private/files"]    = d('@server_master')->web_group;
234

235 236
    $mkdir["sites/$url/private/temp"]     = 02770;
    $chgrp["sites/$url/private/temp"]     = d('@server_master')->web_group;
237 238 239 240 241

    $mkdir["sites/$url/files/styles"]     = 02770;
    $chgrp["sites/$url/files/styles"]     = d('@server_master')->web_group;

    // d8 support
242 243
    $mkdir["sites/$url/private/config"]   = 02770;
    $chgrp["sites/$url/private/config"]   = d('@server_master')->web_group;
244

245 246
    $mkdir["sites/$url/private/config/active"] = 02770;
    $chgrp["sites/$url/private/config/active"] = d('@server_master')->web_group;
247

248 249
    $mkdir["sites/$url/private/config/sync"] = 02770;
    $chgrp["sites/$url/private/config/sync"] = d('@server_master')->web_group;
250
  }
251

252
  // These paths should not have recursive operations performed on them.
253
  $chmod_not_recursive = $chgrp_not_recursive = array(
254 255 256 257 258
    "sites/$url",
    "sites/$url/files",
    "sites/$url/files/tmp",
    "sites/$url/files/images",
    "sites/$url/files/pictures",
259 260 261 262 263
    "sites/$url/files/css",
    "sites/$url/files/js",
    "sites/$url/files/ctools",
    "sites/$url/files/imagecache",
    "sites/$url/files/locations",
264 265
    "sites/$url/private",
    "sites/$url/private/files",
266 267
    "sites/$url/private/temp",
    "sites/$url/files/styles",
268
    "sites/$url/files/private",
269 270
    "sites/$url/private/config",
    "sites/$url/private/config/active",
271
    "sites/$url/private/config/sync"
272 273
  );

274 275 276
  // Allow other commands to add or alter the directories to be created,
  // chmod'd or chgrp'd.
  // TODO: Figure out a better way to do this.
277 278
  drush_command_invoke_all_ref('provision_drupal_create_directories_alter', $mkdir, $url);
  drush_command_invoke_all_ref('provision_drupal_chgrp_directories_alter', $chgrp, $url);
279 280
  drush_command_invoke_all_ref('provision_drupal_chgrp_not_recursive_alter', $chgrp_not_recursive, $url);
  drush_command_invoke_all_ref('provision_drupal_chmod_not_recursive_alter', $chmod_not_recursive, $url);
281

282
  foreach ($mkdir as $path => $perm) {
283
    if (!is_dir($path)) {
284
      provision_file()->mkdir($path)
285 286
        ->succeed('Created <code>@path</code>')
        ->fail('Could not create <code>@path</code>', 'DRUSH_PERM_ERROR');
287
    }
288

289
    if ($perm !== FALSE) {
290
      provision_file()->chmod($path, $perm, !in_array($path, $chmod_not_recursive))
291 292 293
        ->succeed('Changed permissions of <code>@path</code> to @perm')
        ->fail('Could not change permissions <code>@path</code> to @perm');
    }
294
  }
295
  foreach ($chgrp as $path => $group) {
296
    if ($group !== FALSE) {
297
      provision_file()->chgrp($path, $group, !in_array($path, $chgrp_not_recursive))
298 299 300
        ->succeed('Changed group ownership of <code>@path</code> to @gid')
        ->fail('Could not change group ownership <code>@path</code> to @gid');
    }
301 302 303
  }
}

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
/**
 * Call the core file_create_htaccess() functions.
 *
 * Unlink the files first to avoid permission issues.
 * If drupal already created the file then it's owned by e.g. www-data and aegir can not chmod those.
 *
 * @see https://drupal.org/SA-CORE-2013-003
 */
function _provision_drupal_ensure_htaccess_update() {

  if (drush_drupal_major_version() == 7) {

    // Copied from modules/system/system.install system_requirements()
    $htaccess_files['public://.htaccess'] = array(
      'directory' => variable_get('file_public_path', conf_path() . '/files'),
    );
    if ($private_files_directory = variable_get('file_private_path')) {
      $htaccess_files['private://.htaccess'] = array(
        'directory' => $private_files_directory,
      );
    }
    $htaccess_files['temporary://.htaccess'] = array(
      'directory' => variable_get('file_temporary_path', file_directory_temp()),
    );
    foreach ($htaccess_files as $htaccess_file => $info) {
      // Check for the string which was added to the recommended .htaccess file
      // in the latest security update.
      if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || strpos($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003') === FALSE) {

        // Aegir specific
        @unlink($htaccess_file);
        file_create_htaccess($info['directory'], FALSE);
        drush_log(dt('Updated @file to match http://drupal.org/SA-CORE-2013-003', array('@file' => $info['directory'] . '/.htaccess')), 'notice');
        $path = file_stream_wrapper_get_instance_by_uri($htaccess_file)->getDirectoryPath();
        d()->service('http')->sync(d()->root . '/' . $info['directory'] . '/.htaccess');
      }
    }
  }
  elseif (drush_drupal_major_version() == 6 && function_exists('file_create_htaccess')) {

    // Copied from modules/system/system.install system_requirements()
    $htaccess_files['files_htaccess'] = array(
      'directory' => file_directory_path(),
    );
    $htaccess_files['temporary_files_htaccess'] = array(
      'directory' => file_directory_temp(),
    );
    foreach ($htaccess_files as $key => $info) {
      // Check for the string which was added to the recommended .htaccess file
      // in the latest security update.
      $htaccess_file = $info['directory'] . '/.htaccess';
      if (!file_exists($htaccess_file) || !($contents = @file_get_contents($htaccess_file)) || strpos($contents, 'Drupal_Security_Do_Not_Remove_See_SA_2013_003') === FALSE) {

        // Aegir specific
        @unlink($info['directory'] . '/.htaccess');
        file_create_htaccess($info['directory'], NULL);
        drush_log(dt('Updated @file to match http://drupal.org/SA-CORE-2013-003', array('@file' => $info['directory'] . '/.htaccess')), 'notice');
        d()->service('http')->sync(d()->root . '/' . $info['directory'] . '/.htaccess');
      }
    }
  }
}

367 368 369
/**
 * Runs an external script to reload all the various drupal caches
 */
370
function _provision_drupal_rebuild_caches() {
drumm's avatar
drumm committed
371
  if (d()->type === 'site') {
372
    drush_include_engine('drupal', 'clear');
373
  }
374 375
}

376 377 378 379
/**
 * Find available profiles on this platform.
 */
function _provision_find_profiles() {
omega8cc's avatar
omega8cc committed
380 381
  if (drush_drupal_major_version() >= 8) {
    include_once('core/includes/install.inc');
382 383
    $profiles_subdirs[] = "./core/profiles";
    $profiles_subdirs[] = "./profiles";
omega8cc's avatar
omega8cc committed
384 385 386
  }
  else {
    include_once('includes/install.inc');
387
    $profiles_subdirs[] = "./profiles";
omega8cc's avatar
omega8cc committed
388
  }
389 390 391 392
  foreach($profiles_subdirs as $profiles_subdir) {
    if (!$dir = opendir($profiles_subdir)) {
      drush_log(dt("Cannot find profiles directory"), 'error');
      return FALSE;
393
    }
394 395
    while (FALSE !== ($name = readdir($dir))) {
      $languages = array();
396 397
      $file = "$profiles_subdir/$name/$name.profile";
      if ($name == '..' || $name == '.' || !file_exists($file)) {
398 399 400 401
        continue;
      }
      $profile = new stdClass();
      $profile->name = $name;
402
      $profile->filename = $file;
403

404
      $profile->info = array();
405

omega8cc's avatar
omega8cc committed
406
      if (drush_drupal_major_version() >= 8) {
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
        $yaml_file = "$profiles_subdir/$name/$name.info.yml";
        if(!file_exists($yaml_file)) {
          drush_log(dt("@name.info.yml not found.", array("@name" => $name)), 'notice');
          unset($files[$name]);
          continue;
        }
        $profile->info = Symfony\Component\Yaml\Yaml::parse($yaml_file);
        if (!empty($profile->info['name'])) {
          $profile->name = $profile->info['name'];
        }
      }
      else {
        $info_file = "$profiles_subdir/$name/$name.info";
        if (file_exists($info_file)) {
          $profile->info = provision_parse_info_file($info_file);
          // Skip hidden profiles
          if (isset($profile->info['hidden']) && $profile->info['hidden'] == 1) {
            continue;
          }
        }
427
      }
428

429 430 431 432 433
      require_once($profile->filename);
      $func = $profile->name . "_profile_details";
      if (function_exists($func)) {
        $profile->info = array_merge($profile->info, $func());
      }
434

435 436 437 438 439 440 441 442 443
      $languages['en'] = 1;
      // Find languages available
      $files = array_keys(drush_scan_directory($profiles_subdir . '/' . $name . '/translations', '/\.po$/', array('.', '..', 'CVS'), 0, FALSE, 'filepath'));
      $files = array_merge($files, array_keys(drush_scan_directory($profiles_subdir . '/' . $name , '/\.po$/', array('.', '..', 'CVS'), 0, FALSE, 'filepath')));
      if (is_array($files)) {
        foreach ($files as $file) {
          if (preg_match('!(/|\.)([^\./]+)\.po$!', $file, $langcode)) {
            $languages[$langcode[2]] = 1; // use the language name as an index to weed out duplicates
          }
444
        }
445
      }
446
      $profile->info['languages'] = array_keys($languages);
447

448 449 450 451 452
      // Drupal 7 renamed the default install profile to 'standard'
      // Aegir now allows projects to specify an "old short name" to provide an upgrade path when projects get renamed.
      if ($profile->name == 'standard') {
        $profile->info['old_short_name'] = 'default';
      }
453

454 455 456
      $return[$name] = $profile;
      drush_log(dt('Found install profile %name', array('%name' => $name)));
    }
457 458 459 460 461
  }
  return $return;

}

462
function provision_drupal_find_sites() {
463
  $sites = array();
464 465
  if ($dir = opendir("./sites")) {
    while (FALSE !== ($subdir = readdir($dir))) {
466
      // skip internal directory pointers
467
      if ($subdir != '.' && $subdir != '..') {
468 469 470 471
        $file = "./sites/$subdir/settings.php";
        if (file_exists("$file") && ($subdir != 'default') && !is_link("./sites/$subdir")) {
          $sites[$subdir] = $file;
        }
472 473
      }
    }
474
    closedir($dir);
475 476
  }
  else {
477
    drush_log(dt("Cannot find sites directory"), 'error');
478
    $sites = FALSE;
479 480
  }
  return $sites;
481 482
}

483 484 485 486 487 488 489
/**
 * Retrieve a list of aliases for the curent site.
 */
function provision_drupal_find_aliases() {
  $aliases = array();
  if (d()->type === 'site') {
    if (drush_drupal_major_version() >= 7) {
490
      $config = new Provision_Config_Drupal_Alias_Store(d()->name);
491
      $aliases = $config->find();
492 493
    }
    else {
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
      if ($dir = opendir(d()->root . "/sites")) {
        while (FALSE !== ($subdir = readdir($dir))) {
          // skip internal directory pointers
          if ($subdir != '.' && $subdir != '..') {
            $path = d()->root . '/sites/' . $subdir;
            if (is_link($path)) {
              if (d()->uri === readlink($path)) {
                $aliases[] = $subdir;
              }
            }
          }
        }
        closedir($dir);
      }
    }
  }

  return $aliases;
}

514
/**
drumm's avatar
drumm committed
515 516
 * Create and remove symlinks for each of the possible domain aliases of an
 * existing site.
517
 */
518
function _provision_drupal_maintain_aliases() {
drumm's avatar
drumm committed
519
  if (d()->type === 'site') {
520
    if (drush_drupal_major_version() >= 7) {
521
      $config = new Provision_Config_Drupal_Alias_Store(d()->name);
522 523 524
      $config->maintain();
      $config->write();
      d()->service('http')->sync($config->filename());
525 526
    }
    else {
527 528
      _provision_drupal_delete_aliases();
      if (!d()->redirection) {
529
        foreach (d()->aliases as $alias) {
530
          if ($alias = trim($alias)) {
531
            provision_file()->symlink(d()->uri, d()->root . '/sites/' . str_replace('/', '.', $alias))
532 533 534 535
              ->succeed('Created symlink for alias @target')
              ->fail('Could not create symlink for alias @target');
            d()->service('http')->sync(d()->root . '/sites/' . $alias);
          }
536
        }
537 538 539 540 541 542 543 544
      }
    }
  }
}

/**
 * Delete a list of aliases
 */
545 546
function _provision_drupal_delete_aliases() {

547
  if (d()->type === 'site') {
548
    if (drush_drupal_major_version() >= 7) {
549
        $config = new Provision_Config_Drupal_Alias_Store(d()->name);
550 551 552
        $config->delete();
        $config->write();
        d()->service('http')->sync($config->filename());
553 554
    }
    else {
555 556 557 558 559 560 561 562
      $aliases = provision_drupal_find_aliases();
      foreach ($aliases as $alias) {
        $path = d()->root . '/sites/' . $alias;
        provision_file()->unlink($path)
          ->succeed('Removed symlink for alias @path')
          ->fail('Could not remove symlink for alias @path');
        d()->service('http')->sync($path);
      }
563
    }
564 565
  }
}
566

567
function provision_find_packages() {
568
  // Load the version specific include files.
569
  drush_include_engine('drupal', 'packages', drush_drupal_major_version());
570 571 572

  $packages['base'] = _provision_find_packages('base');

573 574
  $packages['sites-all'] = _provision_find_packages('sites', 'all');

575 576 577 578 579
  // Create a package for the Drupal release
  $packages['base']['platforms'] = _provision_find_platforms();

  // Find install profiles.
  $profiles = _provision_find_profiles();
580
  drush_set_option('profiles', array_keys((array) $profiles), 'drupal');
581 582

  // Iterate through the install profiles, finding the profile specific packages
583
  foreach ($profiles as $profile => $info) {
584 585 586 587
    if (!$info->version) {
      $info->version = drush_drupal_version();
    }
    $packages['base']['profiles'][$profile] = $info;
588 589 590 591 592 593 594 595 596
    $packages['profiles'][$profile] =  _provision_find_packages('profiles', $profile);
  }

  return $packages;
}

function _provision_find_platforms() {
  return array(
    'drupal' => array(
597
    'short_name' => 'drupal', 'version' => drush_drupal_version(),
helmo's avatar
helmo committed
598
    'description' => dt("This platform is running @short_name @version", array('@short_name' => 'Drupal', '@version' => drush_drupal_version()))));
599 600 601 602 603 604
}

/**
 * A small helper function to reduce code duplication
 */
function _provision_find_packages($scope, $key = '') {
605
  $packages = array();
606 607
  $scope_text = ($key) ? "$scope/$key" : $scope;
  foreach (array('modules', 'themes') as $type) {
608
    $packages[$type] = array();
609 610 611 612
    $func = "_provision_drupal_find_$type";
    $result = $func($scope, $key);
    if (sizeof($result)) {
      $packages[$type] = $result;
613 614
      drush_log(dt("Found !count !type in !scope",
        array('!count' => sizeof($result),
615 616
        '!scope' => $scope_text, '!type' => $type)));
    }
617 618 619 620
  }
  return $packages;
}

621 622 623 624
/**
 * Map the system table to a packages multi-dimensional array component
 */
function provision_drupal_system_map() {
625
  // Load the version specific include files.
626
  drush_include_engine('drupal', 'packages');
627

628
  return _provision_drupal_system_map();
629 630 631 632 633 634 635 636 637
}

/**
 * Retrieve a list of paths to search in a certain scope
 */
function _provision_drupal_search_paths($scope, $key = '', $type = 'modules') {
  $searchpaths = array();
  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  switch ($scope) {
638
    case 'base' :
639
      $searchpaths[] = sprintf("%s/%s", $drupal_root, $type);
omega8cc's avatar
omega8cc committed
640 641
      $searchpaths[] = sprintf("%s/core/%s", $drupal_root, $type);
      $searchpaths[] = sprintf("%s/sites/all/%s", $drupal_root, $type);
642
      break;
643 644
    default :
      if ($key) {
645 646 647
        $searchpaths[] = sprintf("%s/%s/%s/%s", $drupal_root, $scope, $key, $type);
      }
      break;
648

649 650 651
  }
  return $searchpaths;
}
652

653 654 655 656 657 658 659 660
/**
 * Find modules in a certain scope.
 *
 * This function is general enough that it works for all supported
 * versions of Drupal.
 */
function _provision_drupal_find_modules($scope, $key = '') {
  $paths = _provision_drupal_search_paths($scope, $key, 'modules');
661 662
  $files = array();
  foreach ($paths as $path) {
663
    $files = array_merge($files, drush_scan_directory($path, "/\.module$/", array('.', '..', 'CVS', '.svn'), 0, TRUE, 'name'));
664 665
  }
  foreach ($files as $name => $info) {
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
    if (drush_drupal_major_version() >= 8) {
      $yaml_file = sprintf("%s/%s.info.yml", dirname($info->filename), $name);
      if(!file_exists($yaml_file)) {
        drush_log(dt("@name.info.yml not found.", array("@name" => $name)), 'notice');
        unset($files[$name]);
        continue;
      }
      $files[$name]->info = Symfony\Component\Yaml\Yaml::parse($yaml_file);
      if (!empty($files[$name]->info['name'])) {
        $files[$name]->name = $files[$name]->info['name'];
      }
    }
    else {
      $info_file = sprintf("%s/%s.info", dirname($info->filename), $name);
      $files[$name]->info = provision_parse_info_file($info_file);
    }
682 683 684 685 686
    // Skip hidden modules
    if (isset($files[$name]->info['hidden']) && $files[$name]->info['hidden'] != FALSE) {
      unset($files[$name]);
      continue;
    }
687
    $install_file = sprintf("%s/%s.install", dirname($info->filename), $name);
688
    $schema_version = 0;
689 690 691 692 693
    if (file_exists($install_file)) {
      $source = file_get_contents(trim($install_file));
      $source = str_replace("\r\n", "\n", $source);
      $source = str_replace("\r", "\n", $source);
      $function_matches = array();
694
        preg_match_all('!function\s*&?([a-zA-Z0-9_]+)_update_([0-9]+)\s*\(.*?\s*\{!', $source, $function_matches);
695

696
      if (sizeof($function_matches[0])) {
697
        $schema_version = max($function_matches[2]);
698 699 700
      }
    }
    $files[$name]->schema_version = $schema_version;
701
    $files[$name]->version = $files[$name]->info['version'];
702 703 704 705 706 707

    // Resolve the 'VERSION' constant used in Git checkouts.
    if ($files[$name]->version == 'VERSION') {
      $files[$name]->version = drush_drupal_version();
    }

708 709 710 711 712 713 714
  }
  return $files;
}

function provision_parse_info_file($filename) {
  $info = array();

715
  $defaults = array(
716 717 718 719 720
    'dependencies' => array(),
    'description' => '',
    'version' => NULL,
    'php' => DRUPAL_MINIMUM_PHP,
  );
721

722
  if (file_exists($filename)) {
723
    $info = _provision_drupal_parse_info_file($filename);
724 725 726 727 728 729
  }

  // Merge in defaults and return
  return $info + $defaults;
}

730
/**
731
 * Set up the $_SERVER environment variable so that drupal can correctly parse the settings.php file.
732 733
 * The real credentials are stored in the Apache vhost of the relevant site, to prevent leaking of
 * sensitive data to site administrators with PHP access who might otherwise access such credentials
734
 * potentially of other sites' settings.php in a multisite set-up.
735 736
 */
function provision_prepare_environment() {
737
  $fields = array('db_type', 'db_host', 'db_user', 'db_passwd', 'db_name', 'db_port');
738
  foreach ($fields as $key) {
739
    $_SERVER[$key] = drush_get_option($key, NULL, 'site');
740
  }
741 742 743 744 745

  // As of Drupal 7 there is no more mysqli type
  if (drush_drupal_major_version() >= 7) {
    $_SERVER['db_type'] = ($_SERVER['db_type'] == 'mysqli') ? 'mysql' : $_SERVER['db_type'];
  }
746
}
747 748 749 750 751


/**
 * Reload drushrc files (if available) from several possible locations.
 *
752
 * Because the base drush_load_config method only uses an include_once,
753 754 755 756 757 758
 * we run into issues when provision commands call other commands that
 * modify these config files.
 *
 * For the changes to become available, and more importantly passed to the
 * front end, we need to call this function after calling provision commands.
 */
759
function provision_reload_config($context, $file = NULL) {
760
  $file = ($file) ? $file : _drush_config_file($context);
761
  if (file_exists($file)) {
762
    drush_log("Reloading $context drushrc.php from $file");
763
    include($file);
764
    // $options will be defined by the config file included above.
765 766 767 768 769 770
    if (sizeof($options)) {
      $options = array_merge(drush_get_context($context, array()), $options);
      drush_set_context($context, $options);
    }
  }
}
771 772 773 774 775 776 777 778 779 780 781 782

/**
 * Maintain a symlink to the site within a client directory
 *
 * This creates a directory structure like this:
 *
 * ~/clients/foo/example.org -> ~/platforms/.../sites/example.org
 * ~/clients/bar/bar.example.com -> ~/platforms/.../sites/bar.example.com
 *
 * @todo this probably doesn't belong in this file
 */
function _provision_client_create_symlink() {
783 784
  if (d()->client_name) {
    $sites_dir = d()->server->clients_path . '/' . d()->client_name;
785
    provision_file()->create_dir($sites_dir, dt('Client home directory for @client', array('@client' => d()->client_name)), 0750);
786
    _provision_client_delete_old_symlink();
787 788 789 790
    provision_file()->symlink(d()->site_path, $sites_dir . '/' . d()->uri)
      ->succeed('Created symlink @path to @target')
      ->fail('Could not create symlink @path to @target: @reason');
  }
791 792
}

793 794 795 796 797 798 799 800 801 802
/**
 * Delete dangling symlinks for this site.
 *
 * This is a crude implementation, as we do not have the old client name so we
 * need to iterate over the directories. We only remove the first entry we
 * find to save some I/O.
 */
function _provision_client_delete_old_symlink() {
  $previous = d()->server->clients_path . '/' . d()->client_name . '/' . d()->uri;
  // this is necessary because unlink doesn't fail on missing files (!)
803
  $found = (is_file($previous) || is_link($previous));
804 805 806 807 808 809 810 811
  provision_file()->unlink($previous);
  if (!$found) {
    drush_log(dt("couldn't find previous client symlink, iterating through all sites"));
    // only iterate if the symlink location changed
    if ($dh = @opendir(d()->server->clients_path)) {
      while (($file = readdir($dh)) !== false) {
        if ($file != '.' && $file != '..') {
          $path = d()->server->clients_path . '/' . $file . '/' . d()->uri;
812
          if (is_file($path) || is_link($path)) {
813 814 815 816 817 818 819 820 821 822 823
            provision_file()->unlink($path);
            drush_log(dt("removed previous symlink in @path", array("@path" => $path)), 'success');
            break; // found it
          }
        }
      }
      closedir($dh);
    }
  }
}

824 825 826 827 828 829 830 831
/**
 * Delete the site symlink within the client directory
 *
 * This deletes the site symlink created on verify/install
 *
 * @see _provision_client_create_symlink()
 */
function _provision_client_delete_symlink() {
832 833 834 835 836
  if (d()->client_name) {
    provision_file()->unlink(d()->server->clients_path . '/' . d()->client_name . '/' . d()->uri)
      ->succeed('Deleted symlink @path')
      ->fail('Failed to delete symlink @path: @reason');
  }
837
}