Newer
Older
<?php
// $Id$
/**
* Provision configuration generation classes.
*/
class provisionConfig {
/**
* Template file, a PHP file which will have access to $this and variables
* as defined in $data.
*/
/**
* Associate array of variables to make available to the template.
*/
* A provisionContext object thie configuration relates to.
* @var provisionContext
public $context = null;
/**
* Octal Unix mode for permissons of the created file.
*/
Adrian Rossouw
committed
protected $mode = NULL;
Adrian Rossouw
committed
protected $group = NULL;
/**
* An optional data store class to instantiate for this config.
*/
protected $data_store_class = NULL;
/**
* The data store.
*/
Adrian Rossouw
committed
public $store = NULL;
* Forward $this->... to $this->context->...
Adrian Rossouw
committed
function __get($name) {
if (isset($this->context)) {
return $this->context->$name;
Adrian Rossouw
committed
}
}
* @param $context
* An alias name for d(), the provisionContext that this configuration
* An associative array to potentially manipulate in process() and make
function __construct($context, $data = array()) {
if (is_null($this->template)) {
throw new Exception(dt("No template specified for: %class", array('%class' => get_class($this))));
Adrian Rossouw
committed
// Accept both a reference and an alias name for the context.
$this->context = is_object($context) ? $context : d($context);
if (!is_null($this->data_store_class) && class_exists($this->data_store_class)) {
$class = $this->data_store_class;
$this->store = new $class($context, $data);
Adrian Rossouw
committed
if (is_object($this->store)) {
$this->data['records'] = array_filter(array_merge($this->store->loaded_records, $this->store->records));
}
function filename() {
return false;
}
private function load_template() {
$reflect = new reflectionObject($this);
if (isset($this->template)) {
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);
/**
* Render template, making variables available from $variables associative
* array.
*/
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
}
/**
* 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.
*/
function write() {
$filename = $this->filename();
// Make directory structure if it does not exist.
if (!provision_file()->exists(dirname($filename))->status()) {
provision_file()->mkdir(dirname($filename))
->succeed('Created directory @path.')
->fail('Could not create directory @path.');
}
if ($filename && is_writeable(dirname($filename))) {
// manipulate data before passing to template.
$this->process();
if ($template = $this->load_template()) {
Adrian Rossouw
committed
// Make sure we can write to the file
if (!is_null($this->mode) && !($this->mode & 0200) && provision_file()->exists($filename)->status()) {
provision_file()->chmod($filename, $this->mode | 0200)
Adrian Rossouw
committed
->succeed('Changed permissions of @path to @perm')
->fail('Could not change permissions of @path to @perm');
}
$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();
Adrian Rossouw
committed
// Change the permissions of the file if needed
if (!is_null($this->mode)) {
provision_file()->chmod($filename, $this->mode)
Adrian Rossouw
committed
->succeed('Changed permissions of @path to @perm')
->fail('Could not change permissions of @path to @perm');
}
if (!is_null($this->group)) {
provision_file()->chgrp($filename, $this->group)
Adrian Rossouw
committed
->succeed('Change group ownership of @path to @gid')
->fail('Could not change group ownership of @path to @gid');
// 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');
}
/**
* Remove configuration file as specified by filename().
*/
return provision_file()->unlink($this->filename())->status();
Adrian Rossouw
committed
/**
* 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);
}
function __construct($context, $data = array()) {
parent::__construct($context, $data);
Adrian Rossouw
committed
// 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);
}
}
/**
* Class to write an alias records.
*/
class provisionConfig_drushrc_alias extends provisionConfig_drushrc {
public $template = 'provision_drushrc_alias.tpl.php';
* @param $name
* String '\@name' for named context.
* @param $options
* Array of string option names to save.
function __construct($context, $data = array()) {
parent::__construct($context, $data);
'aliasname' => ltrim($context, '@'),
'options' => $data,
}
function filename() {
return drush_server_home() . '/.drush/' . $this->data['aliasname'] . '.alias.drushrc.php';
/**
* Server level config for drushrc.php files.
*/
class provisionConfig_drushrc_server extends provisionConfig_drushrc {
protected $context_name = 'user';
public $description = 'Server drush configuration';
}
Adrian Rossouw
committed
/**
* Class for writing $platform/drushrc.php files.
*/
class provisionConfig_drushrc_platform extends provisionConfig_drushrc {
protected $context_name = 'drupal';
public $description = 'Platform Drush configuration file';
// platforms contain no confidential information
protected $mode = 0444;
Adrian Rossouw
committed
function filename() {
return $this->root . '/drushrc.php';
}
Adrian Rossouw
committed
}
/**
* 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';
Adrian Rossouw
committed
function filename() {
return $this->site_path . '/drushrc.php';
}
Adrian Rossouw
committed
}
/**
* 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.
*/
class provisionConfig_data_store extends provisionConfig {
public $template = 'data_store.tpl.php';
public $key = 'record';
private $locked = FALSE;
protected $fp = null;
public $records = array();
Adrian Rossouw
committed
public $loaded_records = array();
Adrian Rossouw
committed
protected $mode = 0700;
function __construct($context, $data = array()) {
parent::__construct($context, $data);
/**
* Ensure the file pointer is closed and the lock released upon destruction.
*/
function __destruct() {
// release the file lock if we have it.
$this->close();
}
/**
* Open the file.
*/
Adrian Rossouw
committed
if (!is_resource($this->fp)) {
$this->fp = fopen($this->filename(), "w+");
/**
* 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.
*/
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;
}
}
/**
* Put the contents in the locked file.
*
* We call the lock method here to insure we have the lock.
*/
function file_put_contents($filename, $text) {
$this->lock();
fwrite($this->fp, $text);
fflush($this->fp);
}
/**
* Release the write log on the data store file.
*/
Adrian Rossouw
committed
if ($this->locked && is_resource($this->fp)) {
flock($this->fp, LOCK_UN);
$this->locked = FALSE;
}
}
/**
* Close the file pointer and release the lock (if applicable).
*/
Adrian Rossouw
committed
if (is_resource($this->fp)) {
/**
* Load the data from the data store into our loaded_records property.
*/
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());
Adrian Rossouw
committed
$data_key = $this->key;
if (isset(${$data_key}) && is_array(${$data_key})) {
$this->loaded_records = ${$data_key};
}
/**
* 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.
*/
function merged_records() {
return array_merge($this->loaded_records, $this->records);
}
/**
* Expose the merged records to the template file.
*/
Adrian Rossouw
committed
$this->data['records'] = array_filter(array_merge($this->loaded_records, $this->records));