provision_drupal.module 17.4 KB
Newer Older
1
<?php
2
// $Id$
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/**
 * @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
 * and all the install api code. 
 */

/**
 * Test to see if the site settings.php exists
 *
 * @param url
 *   The url of the site to check
 * @return
 *   If the file exists, return TRUE, else return FALSE.
 */
function _provision_drupal_site_exists($url) {
  return file_exists("sites/$url/settings.php");
}

Adrian Rossouw's avatar
Adrian Rossouw committed
23 24 25
function _provision_drupal_site_installed($url) {
  if (_provision_drupal_site_exists($url)) {
    if ($data = provision_load_site_data($url)) {
26
      return isset($data['installed']) ? $data['installed'] : FALSE;
Adrian Rossouw's avatar
Adrian Rossouw committed
27 28
    }
  }
29
  return FALSE;
Adrian Rossouw's avatar
Adrian Rossouw committed
30
}
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

/**
 * Implentation of hook_provision_backup()
 */
function provision_drupal_provision_backup($url, $data) {
  // Adds the site directory into the backup file
  provision_log("backup", "Adding sites directory to $data[backup_file].gz");
  $result = provision_shell_exec("cd %s; tar -rf %s * ", "sites/$url",  $data['backup_file']);

  if (!$result) {
    provision_log("error", "Could not back up sites directory for drupal");
    provision_set_error(PROVISION_FRAMEWORK_ERROR);
  }
}

46 47 48 49 50 51 52
/**
 * The default template to use while generating config files.
 *
 * @return
 *   The default template for the config file
 */
function _provision_drupal_default_template() {
53
  return file_get_contents(drupal_get_path('module', 'provision_drupal') .'/provision_drupal_settings.tpl.php');
54 55 56 57 58 59 60 61 62 63 64 65
}

/**
 * Generate a settings file for the site.
 *
 * @param url
 *   The url of the site being invoked.
 * @param data
 *   A reference to the associated array containing the data for the site. This needs to be a reference, 
 *   because the modules might provide additional information about the site.
 */
function _provision_drupal_create_settings_file($url, &$data) {
66
  provision_log('notice', t("Generate settings.php file"));
67
  if (provision_path("exists", "sites/$url/settings.php")) {
68 69 70
    provision_path("chmod", "sites/$url/settings.php", 0750,
      t('Changed permissions of settings.php to @confirm'),
      t('Could not change permissions of settings.php to @confirm'));
71 72
  }

73
  $fp = fopen("sites/$url/settings.php", "w");
74
  $text = variable_get('provision_drupal_settings_template', _provision_drupal_default_template());
75
  fwrite($fp, "<?php\n". provision_render_config($text, $data));
76
  fclose($fp);
77

78
  # Change the permissions of the file
79 80 81 82 83 84
  provision_path("chmod", "sites/$url/settings.php", 0550,
    t('Changed permissions of settings.php to @confirm'),
    t('Could not change permissions of settings.php to @confirm'));
  provision_path("chgrp", "sites/$url/settings.php", PROVISION_WEB_GROUP,
    t('Change group ownership of settings.php to @confirm'),
    t('Could not change group ownership of settings.php to @confirm'));
85 86 87 88 89 90 91
}

/**
 * Create the directories needed to host a drupal site
 * 
 * Also maintains permissions on existing directories.
 */
92
function _provision_drupal_create_directories($url, $profile = NULL) {  
93
  $paths = array(
94
    "sites/$url"                 => 0750,
95
    "sites/$url/files"           => 02770,
96 97 98 99
    "sites/$url/files/tmp"       => 02770,
    "sites/$url/files/images"    => 02770,
    "sites/$url/files/pictures"  => 02770,
    "sites/$url/themes"          => 02750,
Adrian Rossouw's avatar
Adrian Rossouw committed
100
    "sites/$url/modules"         => 02750,  
101 102 103 104
  );

  foreach ($paths as $path => $perm) {
    if (!is_dir($path)) {
105
      provision_path("mkdir", $path, TRUE, 
106 107
        t("Created <code>@path</code>"),
        t("Could not create <code>@path</code>"),
108
        PROVISION_PERM_ERROR | PROVISION_INSTALL_ERROR );
109
    }
110
    provision_path("chown", $path, PROVISION_SCRIPT_USER, 
111 112 113
      t("Changed ownership of <code>@path</code>"),
      t("Could not change ownership <code>@path</code>"),
      PROVISION_PERM_ERROR | PROVISION_INSTALL_ERROR );
114
    provision_path("chgrp", $path, PROVISION_WEB_GROUP,
115 116 117 118 119 120 121
      t("Changed group ownership of <code>@path</code>"),
      t("Could not change group ownership <code>@path</code>"));

    provision_path("chmod", $path, $perm, 
      t("Changed permissions of <code>@path</code> to @confirm"),
      t("Could not change permissions <code>@path</code> to @confirm"),
      PROVISION_PERM_ERROR | PROVISION_INSTALL_ERROR );
122 123 124 125 126 127 128 129 130 131
  }
}

/**
 * Switch the active database to the newly created database
 *
 * This function tricks Drupal into thinking that it's running on an uninstalled site,
 * so that it can cleanly install the database schema. It also handles switching back to the 
 * main provisioning site.
 */
132
function _provision_drupal_switch_active_site($url = NULL) {
133 134 135 136 137
  static $backups;
  if ($url) {
    /* Pretend to be the site being installed */
    
    // Fake the necessary HTTP headers that Drupal needs:
138 139
    $_SERVER['HTTP_HOST'] = $url;
    $_SERVER['PHP_SELF'] = '/index.php';
140 141

    $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; // Coder generates a warning. But this is correct.
142 143 144 145 146 147 148 149 150 151
    $_SERVER['REMOTE_ADDR'] = NULL;
    $_SERVER['REQUEST_METHOD'] = NULL;

    /**
     * This code is sourced from bootstrap.inc. I am trying to avoid patching core, but it might
     * be smarter to make a small patch to allow core to re-initialize itself more easily
     */

    // Export the following settings.php variables to the global namespace
    global $base_url, $base_path, $base_root;
152
    global  $cookie_domain, $conf, $profile, $profile, $db_prefix;
153
 
154
    // This is just for backup, to be able to restore to the old DRUSH system.
155
    $backups = compact("active_db", "base_url", "base_path", "db_prefix", "cookie_domain", "conf", "profile");
156
    
157
    include_once $_SERVER['DOCUMENT_ROOT'] .'sites/'. $url .'/settings.php';
158 159 160 161 162 163 164 165 166 167 168 169

    // Create base URL
     $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
     $base_url = $base_root .= '://'. preg_replace('/[^a-z0-9-:._]/i', '', $_SERVER['HTTP_HOST']);
     if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) {
       $base_path = "/$dir";
       $base_url .= $base_path;
       $base_path .= '/';
     }
     else {
       $base_path = '/';
     }
170
     provision_set_active_db($db_url);
171

172
  }
173 174 175 176 177 178 179
  else {
    /**
    * Restore everything to the way it was before we switched sites.
    */
    // Fake the necessary HTTP headers that Drupal needs:
    $drupal_base_url = parse_url(DRUSH_URI);
    $_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
180 181 182
    $_SERVER['PHP_SELF'] = $drupal_base_url['path'] .'/index.php';
    
    $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; // Coder generates a warning, but this is correct.
183 184 185 186
    $_SERVER['REMOTE_ADDR'] = NULL;
    $_SERVER['REQUEST_METHOD'] = NULL;
    
    global $base_url, $base_path, $base_root;
187
    // Export the following settings.php variables to the global namespace
188
    global $db_prefix, $cookie_domain, $conf, $profile;
189

190
    // This is just for backup, to be able to restore to the old DRUSH system.
191
    extract($backups, EXTR_OVERWRITE);
192
    provision_set_active_db();
193 194 195
  }
}

196 197 198 199 200
function provision_drupal_provision_post_restore($url, $data) {
  _provision_drupal_create_settings_file($url, $data);
}

function provision_drupal_provision_restore($url, $data) {
201 202
  $old = PROVISION_SITES_PATH ."/$url.restore";
  $new = PROVISION_SITES_PATH ."/$url";
203 204 205 206 207 208 209 210 211 212 213 214 215 216
  provision_path("switch_paths", $old, $new ,
    t('Swapping out the @path and @confirm directories was successful.'),
    t('Swapping the @path and @confirm directories has failed.'),
    PROVISION_PERM_ERROR);
  // make sure it has the latest site data available
  provision_save_site_data($url, $data);
}

// Luckily this is reversable =)
function provision_drupal_provision_restore_rollback($url, $data) {
  provision_drupal_provision_restore($url, $data);
}


217 218 219
/**
 * Force drupal to load the modules it expects to find on an uninstalled site
 */
220
function _provision_drupal_force_load_modules($url = NULL) {
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
  static $backup_list;
  if ($url) {
    $backup_list = module_list();
    require_once './modules/system/system.install';
    require_once './includes/file.inc';
    require_once './includes/install.inc';
    // Load module basics (needed for hook invokes).
     include_once './includes/module.inc';
     $module_list['system']['filename'] = 'modules/system/system.module';
     $module_list['filter']['filename'] = 'modules/filter/filter.module';
     module_list(TRUE, FALSE, FALSE, $module_list);
     drupal_load('module', 'system');
     drupal_load('module', 'filter');
     #should i load all the other modules? i don't know .=\
  }
  else {
    module_list(TRUE, FALSE, FALSE, $backup_list);
  }
}

/**
 * Install the drupal schema and install profile
 */
244
function _provision_drupal_install_schema($profile, $language = 'en', $client_email = NULL) {
245 246
  include_once('includes/locale.inc');

247
  $GLOBALS['profile'] = $profile;
248
  $GLOBALS['install_locale'] = $language;
Adrian Rossouw's avatar
Adrian Rossouw committed
249
  provision_log("install", t("Installing Drupal schema"));
250 251 252 253 254 255 256 257 258 259 260 261 262 263
  // Load the profile.
  require_once "./profiles/$profile/$profile.profile";

  $requirements = drupal_check_profile($profile);
  $severity = drupal_requirements_severity($requirements);

  // If there are issues, report them.
  if ($severity == REQUIREMENT_ERROR) {
    foreach ($requirements as $requirement) {
      if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
        drupal_set_message($requirement['description'] .' ('. st('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) .')', 'error');
      }
    }

264
    return FALSE;
265 266 267
  }

  // Verify existence of all required modules.
268
  $modules = drupal_verify_profile($profile, $language);
269 270

  if (!$modules) {
271
    provision_set_error(PROVISION_FRAMEWORK_ERROR);
272
    return FALSE;
273
  }
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
 // install system.module
  drupal_install_system();
  $modules = array_diff($modules, array('system'));

  // install other modules
  $files = module_rebuild_cache();
  $operations = array();
  foreach ($modules as $module) {
    _drupal_install_module($module);
    module_enable(array($module));
  }

  // Rebuild menu to get content type links registered by the profile,
  // and possibly any other menu items created through the tasks.
  menu_rebuild();

  // Register actions declared by any modules.
  actions_synchronize();
292

293 294 295
  // Randomize query-strings on css/js files, to hide the fact that
  // this is a new install, not upgraded yet.
  _drupal_flush_css_js();
296 297 298 299 300 301

  // Show profile finalization info.
  $function = $profile .'_profile_final';
  if (function_exists($function)) {
    // More steps required
    $profile_message = $function();
302 303
  }

304
  if ($client_email) {
305
    // create the admin account
306
    $account = user_load(1);
307 308 309 310 311 312
    $edit['name'] = 'admin';
    $edit['pass'] = user_password();
    $edit['mail'] = $client_email;
    $edit['status'] = 1;
    $account = user_save($account,  $edit);

313
    // Mail one time login URL and instructions.
314 315 316 317 318
    $from = variable_get('site_mail', ini_get('sendmail_from'));
    $onetime = user_pass_reset_url($account);
    $variables = array(
      '!username' => $account->name, '!site' => variable_get('site_name', 'Drupal'), '!login_url' => $onetime,
      '!uri' => $base_url, '!uri_brief' => preg_replace('!^https?://!', '', $base_url), '!mailto' => $account->mail, 
319 320
      '!date' => format_date(time()), '!login_uri' => url('user', array('absolute' => TRUE)), 
      '!edit_uri' => url('user/'. $account->uid .'/edit', array('absolute' => TRUE)));
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

    // allow the profile to override welcome email text
    if (file_exists("./profiles/$profile/provision_welcome_mail.inc")) {
      require_once "./profiles/$profile/provision_welcome_mail.inc";
      $mailkey = 'welcome-mail-admin';
    }
    elseif (file_exists(dirname(__FILE__) . '/provision_welcome_mail.inc')) { 
      /** use the module provided welcome email
       * We can not use drupal_get_path here,
       * as we are connected to the provisioned site's database
       */
      require_once dirname(__FILE__) . '/provision_welcome_mail.inc';
      $mailkey = 'welcome-mail-admin';
    }
    else {
      // last resort use the user-pass mail text
      $mailkey = 'user-pass';
    }

    if ($mailkey == 'welcome-mail-admin') {
      $subject = st($mail['subject'], $variables);
      $body = st($mail['body'], $variables);
    }
    else {
      $subject = _user_mail_text('pass_subject', $variables);
      $body = _user_mail_text('pass_body', $variables);
    }

    $mail_success = drupal_mail($mailkey, $account->mail, $subject, $body, $from);
350 351 352 353 354 355 356

    if ($mail_success) {
      provision_log('message', t('Sent welcome mail to @client', array('@client' => $client_email)));
    }
    else {
      provision_log('notice', t('Could not send welcome mail to @client', array('@client' => $client_email)));
    }
357
    provision_log('message', t('Login url: !onetime', array('!onetime' => $onetime)));
358 359
  }

360
  variable_set('install_profile', $profile);
361 362
}

363 364 365
/**
 * implementation of provision_verify
 */
366
function provision_drupal_provision_verify($url, &$data) {
367
  provision_path("writable", "sites", TRUE, t("Drupal sites directory is writable by the provisioning script"),
368
    t("Drupal sites directory is not writable by the provisioning script"), PROVISION_PERM_ERROR);
369
  $exists = _provision_create_dir(PROVISION_DRUSHRC_PATH, t('Drush configuration path'), 0700);
Adrian Rossouw's avatar
Adrian Rossouw committed
370 371 372
  $data['modules'] = _provision_drupal_get_cvs_versions(module_rebuild_cache());
  // Find theme engines
  $data['engines'] = drupal_system_listing('\.engine$', 'themes/engines');
373
  $data['profiles'] = _provision_find_profiles();
Adrian Rossouw's avatar
Adrian Rossouw committed
374
  $data['themes'] = system_theme_data();
375
  $data['platform'] = array('short_name' => 'drupal', 'version' => VERSION);  
Adrian Rossouw's avatar
Adrian Rossouw committed
376 377
  provision_log('notice', t("This platform is running @short_name @version", 
    array('@short_name' => 'drupal', '@version' => VERSION)));  
378 379 380
  $sites = provision_drupal_find_sites(); 

  $data['sites'] = array_keys($sites); // return list of hosted sites. used to determine whether or not to import. 
Adrian Rossouw's avatar
Adrian Rossouw committed
381 382
}

383 384 385 386 387 388 389 390 391 392 393 394 395 396
/**
 * Find available profiles on this platform.
 */
function _provision_find_profiles() {
  include_once('includes/install.inc');

  $profiles = file_scan_directory('./profiles', '\.profile$', array('.', '..', 'CVS', '.svn'), 0, TRUE, 'name', 0);
  
  foreach ($profiles as $key => $profile) {
    require_once($profile->filename);
    $func = $profile->name . "_profile_details";
    if (function_exists($func)) {
      $profile->info =  $func();
    }
397 398 399 400 401 402 403

    // Find languages available
    $languages = array_keys(file_scan_directory('./profiles/' . $key, '\.po$', array('.', '..', 'CVS'), 0, FALSE, 'name'));

    array_unshift($languages, 'en');
    $profile->info['languages'] = $languages;

404 405 406 407 408 409
    $return[$key] = $profile;
  }
  return $return;

}

410
/**
411 412
 * Remove any directories for the site in sites
 * This can't be rolled back. so won't even try.
413 414
 */
function provision_drupal_provision_delete($url, $data) {
415
  return _provision_recursive_delete("sites/$url");
416
}
417 418

function provision_drupal_find_sites() {
419 420 421 422 423 424 425
  $sitephp = file_scan_directory('./sites', 'settings\.php$', array('.', '..', 'CVS', '.svn'), 0, TRUE, 'filename', 0);
  foreach ($sitephp as $file => $info) {
    $path = explode("/", $file);
    array_pop($path);
    $sites[array_pop($path)] = $file;
  }
  return $sites;
426 427
}

Adrian Rossouw's avatar
Adrian Rossouw committed
428 429
function _provision_drupal_get_cvs_versions($files) {
  foreach ($files as $modulename => $file) {
430 431 432
      $project = array();
      $project['filename'] = $file->filename;
      $project['name'] = $file->name;
433
      $file->info['description'] = str_replace("\n", "", $file->info['description']);
434 435 436 437 438
      if (empty($project['project'])) {
        $project['project'] = cvs_deploy_get_project_name($project);
      }
      _cvs_deploy_version_alter($file->info['version'], $project);
      $name = ($project['project']) ? $project['project'] : $modulename;
Adrian Rossouw's avatar
Adrian Rossouw committed
439
      $files[$name] = $file; 
440
  }
441

Adrian Rossouw's avatar
Adrian Rossouw committed
442
  return $files;
443
}
444 445 446 447

function provision_drupal_provision_import($url, &$data) {
  $sites = provision_drupal_find_sites();
  foreach ($sites as $site => $file) {
448 449 450
    if ($site == 'default') {
      continue;
    }
451
    $info = _provision_drupal_import_site($site);
452
    if ($info['installed']) {
453 454
      provision_log("notice", "Returning information for $site");
      $data['sites'][$site] = $info;
455 456
    }
  }
457
  //Just make sure our ini settings and the like aren't wiped out.
458
  include(conf_path() ."/settings.php");
459

460 461 462 463 464 465
}

function _provision_drupal_import_site($url) {
  if (!($data = provision_get_site_data($url))) {
    $data = array(); // initialize site data to empty array
  }
466

467
  include("sites/$url/settings.php");
468
  if ($parts = @parse_url($db_url)) {
469 470 471 472 473
    $data['db_type'] = $parts['scheme'];
    $data['db_user'] = $parts['user'];
    $data['db_host'] = $parts['host'];
    $data['db_passwd'] = $parts['pass'];
    $data['db_name'] = substr($parts['path'], 1);
474 475 476 477 478 479

    /**
     * We need to be running AS the site to be able to see what profile it is installed with.
     *
     * This information is no longer stored in settings.php
     */
480 481
    provision_set_active_db($db_url);
    $install_profile =  db_result(db_query("SELECT value FROM {variable} WHERE name='install_profile'"));
482
    $data['profile'] = ($install_profile) ? unserialize($install_profile) : 'default';
483
    $has_locale = db_result(db_query("SELECT status FROM {system} WHERE type='module' AND name='locale'"));
484
    if ($has_locale) {
485
      $locale = db_result(db_query("SELECT locale FROM {locales_meta} WHERE isdefault=1 AND enabled=1"));
486
    }
487
    $data['language'] = ($locale) ? ($locale) : 'en';
488
    provision_close_active_db();
489
    $data['installed'] = TRUE;
490
  }
491
  provision_save_site_data($url, $data);
492

493 494
  return $data;
}
495