provision_drupal.drush.inc 17.8 KB
Newer Older
1
<?php
2
// $Id$
3
4
5
6
7
8
9
10
/**
 * @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. 
 */

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function provision_drupal_drush_help($section) {
  switch ($section) {
    case 'error:PROVISION_CONFIG_NOT_VALID' : 
      return dt('Config file could not be loaded.');
    case 'error:PROVISION_DRUPAL_SITE_INSTALLED' : 
      return dt('Site has already been installed.');
    case 'error:PROVISION_DRUPAL_SITE_NOT_FOUND' : 
      return dt('Site was not found.');
    case 'error:PROVISION_DRUPAL_INSTALL_FAILED' : 
      return dt('Could not complete Drupal installation.');
    case 'error:PROVISION_DRUPAL_UPDATE_FAILED' : 
      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");
    case 'error:PROVISION_REQUIRES_URL' : 
      return dt('You need to specify the URL argument for this command');
  }

}

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

44
45
46
47
48
49
/**
 * Initialize the platform / site
 *
 * This function is executed by provision_invoke, and is responsible
 * for populating the $data context array
 */
50
function provision_drupal_drush_init($url = NULL) {
51
52
53
  $command = drush_get_command();
  $command = explode(" ", $command['command']);
  if ($command[0] == 'provision') {
54

55
56
57
58
59
60
61
62
    if ($url) {
      drush_set_option('uri' , 'http://' . $url);
      provision_load_site_data($url);
      drush_set_default('site_url', $url);
      drush_set_default('profile', 'default');
      drush_set_default('language', 'en');
      drush_set_default('aliases', array());
    }
63

64
65
66
    define('PROVISION_CONTEXT_SITE', ($url) ? TRUE : FALSE);
    define('PROVISION_CONTEXT_PLATFORM', !PROVISION_CONTEXT_SITE);
  }
67
68
69
70
71
72
73
74
75
}

/**
 * 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.
 */
76
function provision_drupal_drush_exit($url = NULL) {
77
78
79
80
81
82
83
84
85
86
87
88
  $command = drush_get_command();
  $command = explode(" ", $command['command']);
  if ($command[0] == 'provision') {
    if (PROVISION_CONTEXT_SITE) {
      if (drush_get_option('installed')) {
        drush_set_option('site_url', drush_get_option('site_url'), 'site');
        drush_set_option('site_id', drush_get_option('site_id'), 'site');
        provision_save_site_data($url);
      }
    }
    else {
      _provision_generate_config();
Adrian Rossouw's avatar
Adrian Rossouw committed
89
    }
90
91
92
93
94
95
96
97
98
  }
}

/**
 * Some commands need to have a url to operate on.
 *  This prints out a message to that effect.
 */
function _provision_drupal_url_required() {
  if (PROVISION_CONTEXT_PLATFORM) {
99
    drush_set_error('PROVISION_REQUIRES_URL');
100
101
102
103
104
105
106
107
108
109
  }
}

/**
 * Validate a site exists, ie: has a settings.php file
 *
 * This will return an error for sites that haven't been created yet
 */
function _provision_drupal_valid_site() {
  if (PROVISION_CONTEXT_SITE) {
110
    if (!_provision_drupal_site_exists(drush_get_option('site_url'))) {
111
      drush_set_error('PROVISION_DRUPAL_SITE_NOT_FOUND');
112
113
    }
  }
114
115
}

116
117
118
119
120
121
122
123
124
125
126
127
/**
 * 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");
}

128
129
130
131
132
133
134
135

/**
 * Validate a site has been installed, by checking it's site.php file. 
 *
 * This will return an error for sites that haven't been installed yet
 */
function _provision_drupal_valid_installed_site() {
  if (PROVISION_CONTEXT_SITE) {
136
    if (!_provision_drupal_site_installed(drush_get_option('site_url'))) {
137
      drush_set_error('PROVISION_DRUPAL_SITE_NOT_FOUND');
138
139
140
141
142
143
    }
  }
}

function _provision_drupal_valid_not_installed_site() {
  if (PROVISION_CONTEXT_SITE) {
144
    if (_provision_drupal_site_installed(drush_get_option('site_url'))) {
145
      drush_set_error('PROVISION_DRUPAL_SITE_INSTALLED');
Adrian Rossouw's avatar
Adrian Rossouw committed
146
147
148
    }
  }
}
149
150

/**
151
 * Test to see if the site has a site.php and has it set to 'installed'
152
 */
153
154
function _provision_drupal_site_installed($url) {
  if (_provision_drupal_site_exists($url)) {
155
156
    provision_load_site_data($url);
    return drush_get_option('installed');
157
  }
158
  return FALSE;
159
160
}

161
162
163
164
165
166
167
/**
 * The default template to use while generating config files.
 *
 * @return
 *   The default template for the config file
 */
function _provision_drupal_default_template() {
168
  return file_get_contents(dirname(__FILE__) .'/provision_drupal_settings.tpl.php');
169
170
171
172
173
174
175
176
177
178
179
}

/**
 * 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.
 */
180
181
182
183
184
185
186
187
188
189
function _provision_drupal_create_settings_file($url = NULL) {
  $options = drush_get_merged_options();

  $options['extra_config'] = "# Extra configuration from modules:\n";
  foreach (drush_command_implements('provision_drupal_config') as $module) {
    $options['extra_config'] .= "# -- $module settings --\n";
    $options['extra_config'] .= module_invoke($module, 'provision_drupal_config', $url) . "\n";
  }


190
  drush_log(dt("Generate settings.php file"));
191
  if (provision_path("exists", "sites/$url/settings.php")) {
192
    provision_path("chmod", "sites/$url/settings.php", 0640,
193
194
      dt('Changed permissions of settings.php to @confirm'),
      dt('Could not change permissions of settings.php to @confirm'));
195
196
  }

197
  $fp = fopen("sites/$url/settings.php", "w");
198
  $text =  _provision_drupal_default_template();
199
  fwrite($fp, "<?php\n". provision_render_config($text, $options));
200
  fclose($fp);
201

202
  # Change the permissions of the file
203
  provision_path("chmod", "sites/$url/settings.php", 0440,
204
205
    dt('Changed permissions of settings.php to @confirm'),
    dt('Could not change permissions of settings.php to @confirm'));
206
207

  provision_path("chgrp", "sites/$url/settings.php", drush_get_option('web_group'),
208
209
    dt('Change group ownership of settings.php to @confirm'),
    dt('Could not change group ownership of settings.php to @confirm'));
210
211
212
213
214
215
216
}

/**
 * Create the directories needed to host a drupal site
 * 
 * Also maintains permissions on existing directories.
 */
217
function _provision_drupal_create_directories($url, $profile = NULL) {  
218
  $paths = array(
219
    "sites/$url"                 => 0755,
220
    "sites/$url/files"           => 02770,
221
222
223
    "sites/$url/files/tmp"       => 02770,
    "sites/$url/files/images"    => 02770,
    "sites/$url/files/pictures"  => 02770,
224
225
226
227
228
229
230
231
    "sites/$url/themes"          => 0755,
    "sites/$url/modules"         => 0755,  
  );
  $grps = array(
    "sites/$url/files",
    "sites/$url/files/tmp",
    "sites/$url/files/images",
    "sites/$url/files/pictures",
232
233
234
235
  );

  foreach ($paths as $path => $perm) {
    if (!is_dir($path)) {
236
      provision_path("mkdir", $path, TRUE, 
237
238
        dt("Created <code>@path</code>"),
        dt("Could not create <code>@path</code>"),
239
        'DRUSH_PERM_ERROR');
240
    }
241
242

    provision_path("chmod", $path, $perm, 
243
244
      dt("Changed permissions of <code>@path</code> to @confirm"),
      dt("Could not change permissions <code>@path</code> to @confirm"),
245
      'DRUSH_PERM_ERROR');
246
247
  }
  foreach ($grps as $path) {
248
      provision_path("chown", $path, drush_get_option('script_user'), 
249
250
      dt("Changed ownership of <code>@path</code>"),
      dt("Could not change ownership <code>@path</code>"),
251
     'DRUSH_PERM_ERROR' );
252
    provision_path("chgrp", $path, drush_get_option('web_group'),
253
254
      dt("Changed group ownership of <code>@path</code>"),
      dt("Could not change group ownership <code>@path</code>"));
255
256
257
  }
}

258
259
260
/**
 * Runs an external script to reload all the various drupal caches
 */
261
function _provision_drupal_rebuild_caches($url = NULL) {
262
  if (PROVISION_CONTEXT_SITE) {
263
    #drush_bootstrap(DRUSH_BOOTSTRAP_DRUPAL_FULL);
264
    drush_include_engine('drupal', 'clear');
265
  }
Adrian Rossouw's avatar
Adrian Rossouw committed
266
267
}

268

269
270
271
272
273
274
/**
 * Find available profiles on this platform.
 */
function _provision_find_profiles() {
  include_once('includes/install.inc');

275
  if (!$dir = opendir("./profiles")) {
276
    drush_log(dt("Cannot find profiles directory"), 'error');
277
278
279
    return FALSE;
  }
  while (FALSE !== ($name = readdir($dir))) {
280
    $languages = array();
281
282
283
284
285
286
287
288
    $file = "./profiles/$name/$name.profile";
    if ($name == '..' || $name == '.' || !file_exists($file)) {
      continue;
    }
    $profile = new stdClass();
    $profile->name = $name;
    $profile->filename = $file;

289
    _provision_cvs_deploy($profile);
290
291
292
293
294
    require_once($profile->filename);
    $func = $profile->name . "_profile_details";
    if (function_exists($func)) {
      $profile->info =  $func();
    }
295

296
    $languages['en'] = 1;
297
    // Find languages available
298
    $files = array_keys(drush_scan_directory('./profiles/' . $name . '/translations', '\.po$', array('.', '..', 'CVS'), 0, FALSE, 'filepath'));
Adrian Rossouw's avatar
Adrian Rossouw committed
299
    $files = array_merge($files, array_keys(drush_scan_directory('./profiles/' . $name , '\.po$', array('.', '..', 'CVS'), 0, FALSE, 'filepath')));
300
301
302
303
304
    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
        }
305
306
      }
    }
Adrian Rossouw's avatar
Adrian Rossouw committed
307
    $profile->info['languages'] = array_keys($languages);
anarcat's avatar
anarcat committed
308
    $return[$name] = $profile;
309
    drush_log(dt('Found install profile %name', array('%name' => $name)));
310
  }
311

312
313
314
315
  return $return;

}

316
function provision_drupal_find_sites() {
317
318
319
  if ($dir = opendir("./sites")) {
    while (FALSE !== ($subdir = readdir($dir))) {
      $file = "./sites/$subdir/settings.php";
320
      if (file_exists("$file") && ($subdir != 'default') && !is_link("./sites/$subdir")) {
321
322
323
        $sites[$subdir] = $file;
      }
    }
324
    closedir($dir);
325
  } else {
326
    drush_log(dt("Cannot find sites directory"), 'error');
327
    $sites = FALSE;
328
329
  }
  return $sites;
330
331
}

Adrian Rossouw's avatar
Adrian Rossouw committed
332
333
function _provision_drupal_get_cvs_versions($files) {
  foreach ($files as $modulename => $file) {
334
335
336
      $project = array();
      $project['filename'] = $file->filename;
      $project['name'] = $file->name;
337
      $file->info['description'] = str_replace("\n", "", $file->info['description']);
338
339
340
341
342
      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
343
      $files[$name] = $file; 
344
  }
345

Adrian Rossouw's avatar
Adrian Rossouw committed
346
  return $files;
347
}
348

349
350
351
/**
 * Create and remove symlinks for each of the possible domain aliases of an existing site
 */
352
function _provision_drupal_maintain_aliases($url) {
353
  if (PROVISION_CONTEXT_SITE) {
354
    $old_aliases = drush_get_option('aliases', array(), 'site');
355
356
357

    if (!is_array($old_aliases)) {
      $old_aliases = explode(",", $old_aliases);
358
    }
359

360
361
362
363
   /**
     * First we remove all the old aliases
     */
    _provision_drupal_delete_aliases($old_aliases);
364

365
366
367
368
369
370
    $aliases = explode("," , drush_get_option('aliases'));
    if (!is_array($aliases)) {
      $aliases = explode(",", $aliases);
    }
    foreach($aliases as $alias) {
      if (trim($alias)) {
371
        provision_path("symlink", $url, drush_get_option('docroot_path') . "/sites/" . $alias, 
372
373
          dt("Created symlink for alias @alias", array("@alias" => $alias)), 
          dt("Could not create symlink for alias @alias", array("@alias" => $alias)));
374
375
376
377
378
379
380
381
382
383
      }
    }
  }
}

/**
 * Delete a list of aliases
 */
function _provision_drupal_delete_aliases($aliases) {
  foreach ($aliases as $alias) {
384
385
386
387
388
    if (trim($alias)) {
      provision_path("unlink", drush_get_option('docroot_path') . "/sites/" . $alias, TRUE,
            dt("Removed symlink for alias @alias", array("@alias" => $alias)), 
            dt("Could not remove symlink for alias @alias", array("@alias" => $alias)));
    }
389
390
  }
}
391

392
393
394
require_once('cvs_deploy.inc');

function provision_find_packages() {
395
  // Load the version specific include files.
396
  drush_include_engine('drupal', 'packages');
397
398
399
400
401
402
403
404
405
406
407

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

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

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

  // Iterate through the install profiles, finding the profile specific packages
408
  foreach ($profiles as $profile => $info) {
409
410
411
412
413
    _provision_cvs_deploy($info);
    if (!$info->version) {
      $info->version = drush_drupal_version();
    }
    $packages['base']['profiles'][$profile] = $info;
414
415
416
417
418
419
    $packages['profiles'][$profile] =  _provision_find_packages('profiles', $profile);
  }

  // Iterate through the sites, finding site specific packages
  foreach (drush_get_option('sites', array()) as $site) {
    $packages['sites'][$site] = _provision_find_packages('sites', $site);
420
  }
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
  return $packages;
}

function _provision_find_platforms() {
  return array(
    'drupal' => array(
    'short_name' => 'drupal', 'version' => drush_drupal_version(), 
    'description' => dt("This platform is running @short_name @version", array('@short_name' => 'Drupal', '@version' => VERSION))));
}

/**
 * A small helper function to reduce code duplication
 */
function _provision_find_packages($scope, $key = '') {
  $scope_text = ($key) ? "$scope/$key" : $scope;
  foreach (array('modules', 'themes') as $type) {
    $func = "_provision_drupal_find_$type";
    $result = $func($scope, $key);
    if (sizeof($result)) {
      $packages[$type] = $result;
      drush_log(dt("Found !count !type in !scope", 
        array('!count' => sizeof($result), 
        '!scope' => $scope_text, '!type' => $type)));
    }
445
446
447
448
  }
  return $packages;
}

449
450
451
452
/**
 * Map the system table to a packages multi-dimensional array component
 */
function provision_drupal_system_map() {
453
  // Load the version specific include files.
454
  drush_include_engine('drupal', 'packages');
455

456
  $profiles = _provision_find_profiles();
457
458
459
460
461
462
463
  foreach ($profiles as $profile => $info) {
    _provision_cvs_deploy($info);
    if (!$info->version) {
      $info->version = drush_drupal_version();
    }
    $profiles[$profile] = $info;
  }
464
  $packages['platforms'] = _provision_find_platforms();
465
466

  $profile = drush_get_option('profile');
467
468
469
470
471
  $packages['profiles'][$profile] = $profiles[$profile];
  $packages['profiles'][$profile]->status = 1;
  
  $result = db_query("SELECT * FROM {system} WHERE type='module'");
  while ($module = db_fetch_object($result)) {
472
473
474
    $info_file = sprintf("%s/%s.info", dirname($module->filename), $module->name);
    $module->info = provision_parse_info_file($info_file);

475
476
477
478
479
480
481
482
483
    _provision_cvs_deploy($module);
    $module->filename = realpath($module->filename);
    $packages['modules'][$module->name] = $module;
  }

  drush_log(dt("Found !count modules", array('!count' => sizeof($packages['modules']))));

  $result = db_query("SELECT * FROM {system} WHERE type='theme'");
  while ($theme = db_fetch_object($result)) {
484
485
    $info_file = sprintf("%s/%s.info", dirname($theme->filename), $theme->name);
    $theme->info = provision_parse_info_file($info_file);
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
    _provision_cvs_deploy($theme);
    $theme->filename = realpath($theme->filename);
    $packages['themes'][$theme->name] = $theme;
  }
  drush_log(dt("Found !count themes", array('!count' => sizeof($packages['themes']))));
  return $packages;
}

/**
 * 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) {
    case 'base' : 
      $searchpaths[] = sprintf("%s/%s", $drupal_root, $type);
      $searchpaths[] = sprintf("%s/sites/all/%s", $drupal_root, $type);
      break;
    default : 
      if ($key) { 
        $searchpaths[] = sprintf("%s/%s/%s/%s", $drupal_root, $scope, $key, $type);
      }
      break;
     
  }
  return $searchpaths;
}
514

515
516
517
518
519
520
521
522
/**
 * 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');
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
  $files = array();
  foreach ($paths as $path) {
    $files = array_merge($files, drush_scan_directory($path, ".module$", array('.', '..', 'CVS', '.svn'), 0, true, 'name'));
  }
  foreach ($files as $name => $info) {
    $install_file = sprintf("%s/%s.install", dirname($info->filename), $name);
    $schema_version = 1;
    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();
        preg_match_all('!function\s*&?([a-zA-Z0-9_]+)_update_([0-9]+)\(.*?\s*\{!', $source, $function_matches);
      
      if (sizeof($function_matches[0])) {
        $schema_version = max($function_matches[2]) + 1;
      }
    }
    $info_file = sprintf("%s/%s.info", dirname($info->filename), $name);
    $files[$name]->info = provision_parse_info_file($info_file);
    $files[$name]->schema_version = $schema_version;
    _provision_cvs_deploy($files[$name]);
  }
  return $files;
}

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

552
  $defaults = array(
553
554
555
556
557
558
559
    'dependencies' => array(),
    'description' => '',
    'version' => NULL,
    'php' => DRUPAL_MINIMUM_PHP,
  );
  
  if (file_exists($filename)) {
560
    $info = _provision_drupal_parse_info_file($filename);
561
562
563
564
565
566
  }

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