provision.config.inc 11.8 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

// $Id$

/**
 * Provision configuration generation classes.
 */

class provisionConfig {
drumm's avatar
drumm committed
10 11 12 13
  /**
   * Template file, a PHP file which will have access to $this and variables
   * as defined in $data.
   */
14
  public $template = null;
drumm's avatar
drumm committed
15 16 17 18

  /**
   * Associate array of variables to make available to the template.
   */
19
  public $data = array();
drumm's avatar
drumm committed
20 21

  /**
22
   * A provisionContext object thie configuration relates to.
drumm's avatar
drumm committed
23
   *
24
   * @var provisionContext
drumm's avatar
drumm committed
25
   */
26
  public $context = null;
drumm's avatar
drumm committed
27 28 29 30

  /**
   * If set, replaces file name in log messages.
   */
31
  public $description = null;
drumm's avatar
drumm committed
32 33 34 35

  /**
   * Octal Unix mode for permissons of the created file.
   */
36
  protected $mode = NULL;
drumm's avatar
drumm committed
37 38 39 40

  /**
   * Unix group name for the created file.
   */
41
  protected $group = NULL;
42

43 44 45 46 47 48 49 50
  /**
   * An optional data store class to instantiate for this config.
   */
  protected $data_store_class = NULL;

  /**
   * The data store.
   */
51
  public $store = NULL;
52

drumm's avatar
drumm committed
53
  /**
54
   * Forward $this->... to $this->context->...
drumm's avatar
drumm committed
55 56
   * object.
   */
57
  function __get($name) {
58 59
    if (isset($this->context)) {
      return $this->context->$name;
60 61
    }
  }
drumm's avatar
drumm committed
62 63 64 65

  /**
   * Constructor, overriding not recommended.
   *
66
   * @param $context
67
   *   An alias name for d(), the provisionContext that this configuration
drumm's avatar
drumm committed
68 69
   *   is relevant to.
   * @param $data
mig5's avatar
mig5 committed
70
   *   An associative array to potentially manipulate in process() and make
drumm's avatar
drumm committed
71 72
   *   available as variables to the template.
   */
73
  function __construct($context, $data = array()) {
74 75 76
    if (is_null($this->template)) {
      throw(exception);
    }
77

78 79
    // Accept both a reference and an alias name for the context.
    $this->context = is_object($context) ? $context : d($context);
80

81
    if (sizeof($data)) {
82
      $this->data = $data;
83
    }
84 85 86
    
    if (!is_null($this->data_store_class) && class_exists($this->data_store_class)) {
      $class = $this->data_store_class;
87
      $this->store = new $class($context, $data);
88 89
    }

drumm's avatar
drumm committed
90 91
  }

92
  /**
drumm's avatar
drumm committed
93
   * Process and add to $data before writing the configuration.
94
   *
drumm's avatar
drumm committed
95
   * This is a stub to be implemented by subclasses.
96
   */
97
  function process() {
98 99 100
    if (is_object($this->store)) {
      $this->data['records'] = array_filter(array_merge($this->store->loaded_records, $this->store->records));
    }
101 102 103
    return true;
  }

104
  /**
drumm's avatar
drumm committed
105
   * The filename where the configuration is written.
106
   * 
drumm's avatar
drumm committed
107
   * This is a stub to be implemented by subclasses.
108
   */
109 110 111 112
  function filename() {
    return false;
  }

drumm's avatar
drumm committed
113 114 115
  /**
   * Load template from filename().
   */
116
  private function load_template() {
117 118
    $class_name = get_class($this);

119 120 121
    $reflect = new reflectionObject($this);

    if (isset($this->template)) {
122 123 124 125 126 127 128 129 130 131 132 133 134 135
      while ($class_name) {
        // Iterate through the config file's parent classes until we
        // find the template file to use.
        $reflect = new reflectionClass($class_name);
        $base_dir = dirname($reflect->getFilename());

        $file = $base_dir . '/' . $this->template;

        if (file_exists($file) && is_readable($file)) {
          drush_log("Template loaded: $file");
          return file_get_contents($file);
        }

        $class_name = get_parent_class($class_name);
136
      }
137 138
    } 

139 140 141
    return false;
  }

drumm's avatar
drumm committed
142 143 144 145
  /**
   * Render template, making variables available from $variables associative
   * array.
   */
146 147 148 149 150 151 152 153 154 155 156
  private function render_template($template, $variables) {
    drush_errors_off();
    extract($variables, EXTR_SKIP);  // Extract the variables to a local namespace
    ob_start();                      // Start output buffering
    eval('?>'. $template);                 // Generate content
    $contents = ob_get_contents();   // Get the contents of the buffer
    ob_end_clean();                  // End buffering and discard
    drush_errors_on();
    return $contents;                // Return the contents
  }

drumm's avatar
drumm committed
157 158 159 160 161 162 163 164 165 166
  /**
   * Write out this configuration.
   *
   * 1. Make sure parent directory exists and is writable.
   * 2. Load template with load_template().
   * 3. Process $data with process().
   * 4. Make existing file writable if necessary and possible.
   * 5. Render template with $this and $data and write out to filename().
   * 6. If $mode and/or $group are set, apply them for the new file.
   */
167 168
  function write() {
    $filename = $this->filename();
169
    // Make directory structure if it does not exist.
170 171
    if (!provision_file()->exists(dirname($filename))->status()) {
      provision_file()->mkdir(dirname($filename))
172 173 174 175
        ->succeed('Created directory @path.')
        ->fail('Could not create directory @path.');
    }

176
    $status = FALSE;
177 178 179 180 181
    if ($filename && is_writeable(dirname($filename))) {
      // manipulate data before passing to template.
      $this->process();

      if ($template = $this->load_template()) {
182
        // Make sure we can write to the file
183 184
        if (!is_null($this->mode) && !($this->mode & 0200) && provision_file()->exists($filename)->status()) {
          provision_file()->chmod($filename, $this->mode | 0200)
185 186 187 188
            ->succeed('Changed permissions of @path to @perm')
            ->fail('Could not change permissions of @path to @perm');
        }

189 190 191
        $status = provision_file()->file_put_contents($filename, $this->render_template($template, $this->data))
          ->succeed('Generated config ' . (empty($this->description) ? $filename : $this->description), 'success')
          ->fail('Could not generate ' . (empty($this->description) ? $filename : $this->description))->status();
192 193 194

        // Change the permissions of the file if needed
        if (!is_null($this->mode)) {
195
          provision_file()->chmod($filename, $this->mode)
196 197 198 199
            ->succeed('Changed permissions of @path to @perm')
            ->fail('Could not change permissions of @path to @perm');
        }
        if (!is_null($this->group)) {
200
          provision_file()->chgrp($filename, $this->group)
201 202
            ->succeed('Change group ownership of @path to @gid')
            ->fail('Could not change group ownership of @path to @gid');
203 204 205
        }
      }
    }
206
    return $status;
207 208
  }

209 210 211 212 213 214
  // allow overriding w.r.t locking
  function file_put_contents($filename, $text) {
    provision_file()->file_put_contents($filename, $text)
      ->succeed('Generated config ' . (empty($this->description) ? $filename : $this->description), 'success');
  }

drumm's avatar
drumm committed
215 216 217
  /**
   * Remove configuration file as specified by filename().
   */
218
  function unlink() {
219
    return provision_file()->unlink($this->filename())->status();
220
  }
221
  
222
}
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

/**
 * Specialized class to handle the creation of drushrc.php files.
 *
 * This is based on the drush_save_config code, but has been abstracted
 * for our purposes.
 */ 
class provisionConfig_drushrc extends provisionConfig {
  public $template = 'provision_drushrc.tpl.php';
  public $description = 'Drush configuration file';
  protected $mode = 0400;
  protected $context_name = 'drush';

  function filename() {
    return _drush_config_file($this->context_name);
  }

240 241
  function __construct($context, $data = array()) {
    parent::__construct($context, $data);
242 243
    $this->load_data();
  }
244

245
  function load_data() {
246 247 248 249 250 251 252 253 254 255 256
    // we fetch the context to pass into the template based on the context name
    $this->data = array_merge(drush_get_context($this->context_name), $this->data);
  }

  function process() {
    unset($this->data['context-path']);
    unset($this->data['config-file']);
    $this->data['option_keys'] = array_keys($this->data);
  }
}

257 258 259
/**
 * Class to write an alias records.
 */
260
class provisionConfig_drushrc_alias extends provisionConfig_drushrc {
261 262
  public $template = 'provision_drushrc_alias.tpl.php';

263
  /**
264 265 266 267
   * @param $name
   *   String '\@name' for named context.
   * @param $options
   *   Array of string option names to save.
268
   */
269 270
  function __construct($context, $data = array()) {
    parent::__construct($context, $data);
271
    $this->data = array(
272
      'aliasname' => ltrim($context, '@'),
273
      'options' => $data,
274
    );
275 276 277
  }

  function filename() {
drumm's avatar
drumm committed
278
    return drush_server_home() . '/.drush/' . $this->data['aliasname'] . '.alias.drushrc.php'; 
279 280 281
  }
}

282 283 284 285 286 287 288 289
/**
 * Server level config for drushrc.php files.
 */
class provisionConfig_drushrc_server extends provisionConfig_drushrc {
  protected $context_name = 'user';
  public $description = 'Server drush configuration';
}

290 291 292 293 294 295
/**
 * Class for writing $platform/drushrc.php files.
 */
class provisionConfig_drushrc_platform extends provisionConfig_drushrc {
  protected $context_name = 'drupal';
  public $description = 'Platform Drush configuration file';
296 297 298 299

  function filename() {
    return $this->root . '/drushrc.php';
  }
300 301 302 303 304 305 306 307 308
}

/**
 * Class for writing $platform/sites/$url/drushrc.php files.
 */
class provisionConfig_drushrc_site extends provisionConfig_drushrc {
  protected $context_name = 'site';
  public $template = 'provision_drushrc_site.tpl.php';
  public $description = 'Site Drush configuration file';
309 310 311 312

  function filename() {
    return $this->site_path . '/drushrc.php';
  }
313 314
}

315 316 317 318 319 320 321 322 323 324 325
/**
 * Base class for data storage.
 *
 * This class provides a file locking mechanism for configuration
 * files that may be susceptible to race conditions.
 *
 * The records loaded from the config and the records set in this
 * instance are kept in separate arrays.
 *
 * When we lock the file, we load the latest stored info.
 */
326 327 328 329 330 331 332 333
class provisionConfig_data_store extends provisionConfig {
  public $template = 'data_store.tpl.php';
  public $key = 'record';

  private $locked = FALSE;
  protected $fp = null;

  public $records = array();
334
  public $loaded_records = array();
335

336
  protected $mode = 0700;
337 338


339 340
  function __construct($context, $data = array()) {
    parent::__construct($context, $data);
341 342 343 344

    $this->load_data();
  }

345 346 347
  /**
   * Ensure the file pointer is closed and the lock released upon destruction.
   */
348 349 350 351 352
  function __destruct() {
    // release the file lock if we have it.
    $this->close();
  }

353 354 355
  /**
   * Open the file.
   */
356
  function open() {
357 358
    if (!is_resource($this->fp)) {
      $this->fp = fopen($this->filename(), "w+");
359 360 361
    }
  }

362 363 364 365 366 367
  /**
   * Lock the file from other writes.
   *
   * After the file has been locked, we reload the data from the file
   * so that any changes we make will not override previous changes.
   */
368 369 370 371 372 373 374 375 376 377 378
  function lock() {
    if (!$this->locked) {
      $this->open();
      flock($this->fp, LOCK_EX);

      // Do one last load before setting our locked status.
      $this->load_data();
      $this->locked = TRUE;
    }
  }

379 380 381 382 383
  /**
   * Put the contents in the locked file.
   *
   * We call the lock method here to insure we have the lock.
   */
384 385 386 387 388 389
  function file_put_contents($filename, $text) {
    $this->lock();
    fwrite($this->fp, $text);
    fflush($this->fp);
  }

390 391 392
  /**
   * Release the write log on the data store file.
   */
393
  function unlock() {
394
    if ($this->locked && is_resource($this->fp)) {
395 396 397 398 399
      flock($this->fp, LOCK_UN);
      $this->locked = FALSE;
    }
  }

400 401 402
  /**
   * Close the file pointer and release the lock (if applicable).
   */
403
  function close() {
404
    if (is_resource($this->fp)) {
405 406 407 408
      fclose($this->fp);
    }
  }

409 410 411
  /**
   * Load the data from the data store into our loaded_records property.
   */
412 413 414 415 416 417
  function load_data() {
    if (!$this->locked) {
      // Once we have the lock we dont need to worry about it changing
      // from under us.
      if (file_exists($this->filename()) && is_readable($this->filename())) {
        include($this->filename());
418 419 420 421
        $data_key = $this->key;
        if (isset(${$data_key}) && is_array(${$data_key})) {
          $this->loaded_records = ${$data_key};
        }
422 423 424 425
      }
    }
  }

426 427 428 429 430
  /**
   * Return the merged contents of the records from the data store , and the values set by us.
   *
   * This is basically the data that would be written to the file if we were to write it right now.
   */
431 432 433 434
  function merged_records() {
    return array_merge($this->loaded_records, $this->records);
  }

435 436 437
  /**
   * Expose the merged records to the template file.
   */
438
  function process() {
439
    $this->data['records'] = array_filter(array_merge($this->loaded_records, $this->records));
440 441
  }
}