You are here

hosting.module in Hosting 6.2

Same filename and directory in other branches
  1. 5 hosting.module
  2. 7.4 hosting.module
  3. 7.3 hosting.module

Hosting module.

Contains just about all the interface magic of hostmaster.

File

hosting.module
View source
<?php

/**
 * @file
 *   Hosting module.
 *
 * Contains just about all the interface magic of hostmaster.
 */

/**
 * The maximum length for a site alias.
 * Leaving room to append '.alias.drushrc.php' and keep filenames below 255.
 */
define('HOSTING_MAX_ALIAS_LENGTH', 235);

/**
 * Not split for performance reasons. Just to keep code together.
 */
include_once 'hosting.inc';
include_once 'hosting.queues.inc';
include_once 'hosting.features.inc';

/**
 * Implementation of hook_menu().
 */
function hosting_menu() {
  global $user;
  $items = array();
  $items['hosting/disabled'] = array(
    'title' => 'Site disabled',
    'page callback' => 'hosting_disabled_site',
    'access arguments' => array(
      'access disabled sites',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['hosting/js'] = array(
    'title' => t('ahah callback'),
    'page callback' => 'hosting_js_page',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['hosting/maintenance'] = array(
    'title' => 'Site maintenance',
    'page callback' => 'hosting_site_maintenance',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['welcome'] = array(
    'title' => 'Welcome',
    'page callback' => 'hosting_welcome',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['admin/hosting'] = array(
    'title' => 'Hosting',
    'description' => 'Configure and manage the hosting system',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hosting_features_form',
    ),
    'access arguments' => array(
      'administer hosting',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/hosting/features'] = array(
    'title' => 'Features',
    'description' => 'Configure the exposed functionality of the Hosting system',
    'weight' => -100,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hosting_features_form',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'access arguments' => array(
      'administer hosting features',
    ),
  );
  $items['admin/hosting/queues'] = array(
    'title' => 'Queues',
    'description' => 'Configure the frequency that cron, backup and task events are process',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hosting_queues_configure',
    ),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array(
      'administer hosting queues',
    ),
  );
  $items['admin/hosting/settings'] = array(
    'title' => 'Settings',
    'description' => 'Configure general Hosting settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hosting_settings',
    ),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array(
      'administer hosting settings',
    ),
  );
  $items['hosting/queues'] = array(
    'page callback' => 'hosting_queues',
    'type' => MENU_CALLBACK,
    'access arguments' => array(
      'access task logs',
    ),
  );
  return $items;
}

/**
 * Page callback for a page to be rendered in modal frame.
 */
function hosting_js_page() {
  modalframe_child_js();
  $args = func_get_args();
  $path = implode('/', $args);

  // Incase Aegir is running in a sub-directory the base_path should be stripped away here
  //    Remove the leading slash from base_path as $path doesn't have one either
  $path = str_replace(ltrim(base_path(), '/'), '', $path);
  menu_set_active_item($path);

  #  $_SERVER['REQUEST_URI'] = str_replace('/hosting/js', '', $_SERVER['REQUEST_URI']);
  if ($router_item = menu_get_item($path)) {
    if ($router_item['access']) {
      if ($router_item['file']) {
        require_once $router_item['file'];
      }
      $return = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
    }
    else {
      $return = drupal_access_denied();
    }
  }

  // Menu status constants are integers; page content is a string.
  if (is_int($return)) {
    modalframe_close_dialog();
  }
  return $return;
}

/**
 * Implementation of hook_menu_alter().
 */
function hosting_menu_alter(&$items) {
  $items['node/add']['page callback'] = '_hosting_node_add';
  $types = hosting_feature_node_types(TRUE);
  foreach ($types as $feature => $type) {
    $path = sprintf('node/add/%s', str_replace('_', '-', $type));
    $items[$path]['access callback'] = 'hosting_menu_access';
    $items[$path]['access arguments'] = array(
      str_replace('_', ' ', $type),
      $feature,
    );
  }

  // These node types should remain hidden, and provide no user interface.
  $items['node/add/package']['page callback'] = 'drupal_access_denied';
  $items['node/add/task']['page callback'] = 'drupal_access_denied';
  $items['node/add/package']['type'] = MENU_CALLBACK;
  $items['node/add/task']['type'] = MENU_CALLBACK;
}

/**
 * Menu access callback for creating a node provided by a Hosting feature.
 *
 * @param $type
 *   The node type that the user wants to create.
 * @param $feature
 *   The machine name of the host feature that should be additionally checked to
 *   see if it's enabled.
 * @return
 *   TRUE if the user can access, FALSE otherwise.
 */
function hosting_menu_access($type, $feature) {
  global $user;
  return ($user->uid == 1 || user_access('create ' . $type)) && hosting_feature($feature) != HOSTING_FEATURE_DISABLED;
}

/**
 * Page callback for a site that has been disabled.
 */
function hosting_disabled_site() {
  drupal_set_breadcrumb(array());
  return t("This site has been disabled by the site administrators");
}

/**
 * Page callback for a site that is undergoing maintenance.
 */
function hosting_site_maintenance() {
  drupal_set_breadcrumb(array());
  return t("This site is currently in maintenance. Check back later.");
}

/**
 * Implementation of hook_nodeapi().
 *
 * This function redirects to hosting_nodeapi_$nodetype_$op calls, to save
 * ourselves from an incessant amount of intricately nested code, and allow
 * easier extension / maintenance.
 *
 * @see hook_nodeapi_TYPE_OP()
 */
function hosting_nodeapi(&$node, $op, $arg1, $arg2) {
  global $user;
  $return = array();
  if (in_array($node->type, hosting_context_node_types())) {
    switch ($op) {
      case 'presave':
        if (!isset($node->uid)) {
          $node->uid = $user->uid;
        }
        break;
      case 'load':
        $return = db_fetch_array(db_query("SELECT name AS hosting_name FROM {hosting_context} WHERE nid = %d", $node->nid));
        if ($return === FALSE) {

          // Should only happen on install
          $return = array();
        }
        break;
      case 'delete':
        db_query("DELETE FROM {hosting_context} WHERE nid = %d", $node->nid);
        break;
    }
  }

  // We need to practically re-invent module_invoke_all here because
  // we have to pass the node as a reference.
  $hook = "nodeapi_" . $node->type . "_" . str_replace(" ", "_", $op);
  foreach (module_implements($hook) as $module) {
    $function = $module . '_' . $hook;
    $result = $function($node, $arg1, $arg2);
    if (isset($result) && is_array($result)) {
      $return = array_merge_recursive($return, $result);
    }
    else {
      if (isset($result)) {
        $return[] = $result;
      }
    }
  }
  return $return;
}

/**
 * Implementation of hook_perm().
 */
function hosting_perm() {
  return array(
    'access hosting wizard',
    'administer hosting queues',
    'administer hosting features',
    'administer hosting',
    'access disabled sites',
    'administer hosting settings',
  );
}

/**
 * Per node type description text. To be stored in the node_type table.
 *
 * @param $type
 *   The node type.
 * @return
 *   Description text for the node type.
 */
function hosting_node_help($type) {
  switch ($type) {
    case 'site':
      return t("<strong>An instance of a hosted site.</strong>\n                It contains information relating to the site, most notably the domain name, database server \n                and platform it is being published on. A site may also have several aliases for additional\n                domains the site needs to be accessible on.");
      break;
    case 'platform':
      return t("<strong>The file system location on a specific web server on which to publish sites.</strong>\n                Multiple platforms can co-exist on the same web server, and need to do so for\n                upgrades to be managed, as this is accomplished by migrating the site between platforms.\n                Platforms are most commonly built for specific releases of Drupal.");
      break;
    case 'client':
      return t("<strong>The person or group that runs the site.</strong> \n                This information is usually required for billing and access purposes, to ensure\n                that only certain people are able to view the information for sites they run. \n                If you do not intend on having more than one client access the system, \n                you will not need to create any additional clients for your purposes.");
      break;
    case 'server':
      return t("<strong>The physical machine which will provide various services to your site.</strong>\n                Each server can have multiple services associated to it. To host a site you require a database service and a web service, which can either be provided by a single or multiple servers.");
      break;
    case 'task':
      return t("<strong>The mechanism whereby Hostmaster keeps track of all changes that occur to the system.</strong>\n                Each task acts as a command for the back-end, and contains a full log of all changes that have occurred.\n                If a task should fail, the administrator will be notified with an explanation of exactly what went wrong,\n                and how to fix it.");
      break;
  }
}

/**
 * Implementation of hook_init().
 */
function hosting_init() {

  // Definitions for the default platforms, clients etc.
  // Done to avoid using 'magic numbers'
  define('HOSTING_DEFAULT_CLIENT', variable_get('hosting_default_client', 1));
  define('HOSTING_DEFAULT_DB_SERVER', variable_get('hosting_default_db_server', 2));
  define('HOSTING_DEFAULT_WEB_SERVER', variable_get('hosting_default_web_server', 3));

  /**
   * This client has access to everything, see hosting_client.access.inc
   */
  define('HOSTING_ADMIN_CLIENT', variable_get('hosting_admin_client', 1));
  define('HOSTING_OWN_DB_SERVER', variable_get('hosting_own_db_server', 2));
  define('HOSTING_OWN_WEB_SERVER', variable_get('hosting_own_web_server', 3));
  define('HOSTING_OWN_PLATFORM', variable_get('hosting_own_platform', 6));

  /**
   * Find the base URL, this is used by the initial 'hosting-setup' drush command
   * This gets defined in the bootstrap, so just using the global definition.
   */
  define('HOSTING_DEFAULT_BASE_URL', $GLOBALS['base_url']);

  // moved from hook_menu()
  drupal_add_css(drupal_get_path('module', 'hosting') . '/hosting.css');

  // Redirect anonymous users to our default welcome page
  global $user;
  $welcome = variable_get('hosting_welcome_page', TRUE);
  if ($welcome && !user_is_logged_in() && php_sapi_name() != 'cli' && drupal_is_front_page()) {
    drupal_goto('welcome');
  }
}

/**
 * Provide a welcome page for anonymous users.
 *
 * To override this page, users should simply change the site_frontpage
 * setting to point to another page. The translation system can also be used.
 */
function hosting_welcome() {
  drupal_set_title(t('Welcome to Aegir'));
  $links = array(
    '@www' => url("http://www.aegirproject.org"),
    '@libre' => url("https://en.wikipedia.org/wiki/Free_software"),
    '@community' => url("http://community.aegirproject.org"),
    '@faq' => url("http://community.aegirproject.org/faq"),
    '@handbook' => url("http://community.aegirproject.org/handbook"),
    '@help' => url("http://community.aegirproject.org/help"),
    '@team' => url("http://community.aegirproject.org/maintainers"),
    '@irc' => url("irc://irc.freenode.net/aegir"),
  );
  return '<div>' . t("The <a href=\"@www\" target=\"_blank\">Aegir hosting system</a> allows developers and site administrators to automate many of the common tasks associated with deploying and managing large websites. Aegir makes it easy to install, upgrade, deploy, and backup an entire network of Drupal sites.<br /><br /> Aegir is a <a href=\"@libre\" target=\"_blank\">free (libre) software</a> project. On the <a href=\"@community\" target=\"_blank\">community site</a> you can find answers to <a href=\"@faq\" target=\"_blank\">frequently asked questions</a>, extensive <a href=\"@handbook\" target=\"_blank\">documentation</a> and other <a href=\"@help\" target=\"_blank\">support resources</a>. The <a href=\"@team\" target=\"_blank\">core team</a> and other users are often available to chat via IRC in <a href=\"@irc\" target=\"_blank\">#aegir</a>.", $links) . '</div>';
}

/**
 * Implementation of hook_theme().
 */
function hosting_theme() {
  return array(
    'hosting_queues_configure' => array(
      'file' => 'hosting.module',
      'arguments' => array(
        'form' => NULL,
      ),
    ),
    'hosting_feature_dependencies' => array(
      'file' => 'hosting.features.inc',
      'arguments' => array(
        'dependencies' => NULL,
        'prefix' => NULL,
        'features' => NULL,
      ),
    ),
  );
}
function _hosting_node_link($nid, $title = null) {
  if (is_null($nid)) {
    return t("None");
  }
  $node = node_load($nid);
  $title = !is_null($title) ? $title : filter_xss($node->title);
  if ($node->nid) {
    return node_access('view', $node) ? l($title, "node/" . $node->nid) : filter_xss($node->title);
  }
}

/**
 * Implementation of hook_block().
 */
function hosting_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks['hosting_summary'] = array(
        'info' => t('Hosting summary'),
        'enabled' => 1,
        'region' => 'left',
        'weight' => 10,
      );
      $blocks['hosting_queues_summary'] = array(
        'info' => t('Hosting queues summary'),
        'enabled' => 1,
        'region' => 'right',
        'weight' => 1,
        'cache' => BLOCK_NO_CACHE,
      );
      return $blocks;
    case 'view':
      switch ($delta) {
        case 'hosting_queues_summary':
          return array(
            'title' => t('Queues summary'),
            'content' => hosting_queue_summary_block(),
          );
      }
      break;
  }
}

/**
 * Build a block summarising the hosting queues.
 */
function hosting_queue_summary_block() {
  if (user_access('administer hosting queues')) {
    $queues = hosting_get_queues();
    $output = '';
    foreach ($queues as $queue => $info) {
      $disp = array();

      # special case
      if (!$info['enabled']) {
        $disp[] = t('Status: disabled');
        continue;
      }
      $disp[] = t('Status: enabled');
      foreach (array(
        'description' => t('Description'),
        'frequency' => t('Frequency'),
        'items' => t('Items per run'),
        'total_items' => t('Items in queue'),
        'last_run' => t('Last run'),
      ) as $key => $title) {
        if ($key == 'last_run') {
          $info[$key] = hosting_format_interval($info[$key]);
        }
        elseif ($key == 'frequency') {
          $info[$key] = t('every @interval', array(
            '@interval' => format_interval($info[$key]),
          ));
        }
        $disp[] = $title . ": " . $info[$key];
      }
      $output .= theme('item_list', $disp, $info['name']);
    }
    return $output;
  }
}

/**
 * Check site URL is allowed.
 *
 * This function hooks into hook_allow_domain to let contrib modules
 * weigh in on whether the site should be created.
 *
 * All the hooks must return true for the domain to be allowed.
 *
 * @see hook_allow_domain()
 */
function hosting_domain_allowed($url, $params = array()) {
  $results = module_invoke_all('allow_domain', $url, $params);
  $return = !in_array(FALSE, $results);
  return $return;
}

/**
 * Initial hosting setup drush command.
 * 
 * Runs the 'hosting-dispatch' command, and sets up the crontab.
 */
function drush_hosting_setup() {
  if (drush_confirm("This command will replace your crontab for this user. continue?")) {
    variable_set('hosting_dispatch_enabled', FALSE);

    // attempt to run the dispatch command, to make sure it runs without the queue being enabled.
    variable_set('hosting_dispatch_enabled', TRUE);
    drush_print(_hosting_dispatch_cmd());
    exec(_hosting_dispatch_cmd(), $return, $code);
    variable_set('hosting_dispatch_enabled', FALSE);
    $return = join("\n", $return);
    $data = unserialize($return);
    if ($code == DRUSH_SUCCESS) {
      variable_set('hosting_dispatch_enabled', TRUE);
      drush_log(t("Dispatch command was run successfully"), 'success');
      _hosting_setup_cron();
    }
    else {
      drush_set_error('DRUSH_FRAMEWORK_ERROR', dt("Dispatch command could not be run. Returned: \n@return", array(
        '@return' => $return,
      )));
    }
    if (drush_get_error()) {
      drush_log(t("The command did not complete successfully, please fix the issues and re-run this script"), 'error');
    }
  }
}

/**
 * Set up the hosting-dispatch command in the aegir user's crontab.
 * 
 * Replace the crontab entry if it exists, else create it from scratch.
 */
function _hosting_setup_cron() {
  $existing = FALSE;
  exec('crontab -l 2> /dev/null', $cron);
  variable_set('hosting_cron_backup', $cron);
  if (sizeof($cron)) {
    drush_log(dt("Replacing existing crontab"), 'warning');
  }
  else {
    drush_log(dt("No existing crontab was found"), 'message');
  }
  $cron = hosting_queues_cron_cmd();
  $fp = popen('crontab -', 'w');
  $success = TRUE;
  if ($fp) {
    if (fwrite($fp, $cron) && fwrite($fp, "\n")) {
      drush_log(dt("Installed hosting-dispatch cron entry to run every minute"), 'success');
    }
    else {
      $success = FALSE;
    }

    // pclose returns shell exit codes (ie. 0 is success)
    if (pclose($fp)) {
      $success = FALSE;
    }
  }
  else {
    $success = FALSE;
  }
  if (!$success) {
    drush_set_error('PROVISION_CRON_FAILED', dt('Failed to install cron job'));
  }
}

/**
 * Replacement node/add page.
 *
 * Major kludge to remove the hidden node types from node/add page.
 *
 * Copied from node.module.
 */
function _hosting_node_add($type = '') {
  global $user;
  $types = node_get_types();
  $type = $type ? str_replace('-', '_', $type) : NULL;

  // If a node type has been specified, validate its existence.
  if (isset($types[$type]) && user_access('create ' . $type) && hosting_feature($type) !== HOSTING_FEATURE_DISABLED) {

    // Initialize settings:
    $node = array(
      'uid' => $user->uid,
      'name' => $user->name,
      'type' => $type,
    );
    drupal_set_title(t('Submit @name', array(
      '@name' => $types[$type]->name,
    )));
    $output = drupal_get_form($type . '_node_form', $node);
  }
  else {

    // If no (valid) node type has been provided, display a node type overview.
    foreach ($types as $type) {
      if (function_exists($type->module . '_form') && user_access('create ' . $type->type) && hosting_feature($type->type) !== HOSTING_FEATURE_DISABLED) {
        $type_url_str = str_replace('_', '-', $type->type);
        $title = t('Add a new @s.', array(
          '@s' => $type->name,
        ));
        $out = '<dt>' . l(drupal_ucfirst($type->name), "node/add/{$type_url_str}", array(
          'attributes' => array(
            'title' => $title,
          ),
        )) . '</dt>';
        $out .= '<dd>' . filter_xss_admin($type->description) . '</dd>';
        $item[$type->name] = $out;
      }
    }
    if (isset($item)) {
      uksort($item, 'strnatcasecmp');
      $output = t('Choose the appropriate item from the list:') . '<dl>' . implode('', $item) . '</dl>';
    }
    else {
      $output = t('No content types available.');
    }
  }
  return $output;
}

/**
 * List queues or tasks in a queue if a key is provided.
 *
 * @see hosting_TASK_SINGULAR_list()
 */
function hosting_queues($key = '') {
  $queues = hosting_get_queues();
  if ($queues[$key]) {
    if ($queues[$key]['name']) {
      $output .= "<h1>" . $queues[$key]['name'] . "</h1>";
    }
    $func = 'hosting_' . $queues[$key]['singular'] . '_list';
    if (function_exists($func)) {
      $output .= $func();
    }
  }
  else {
    foreach ($queues as $key => $queue) {
      $item[] = l($queue['name'], 'hosting/queues/' . $key);
    }
    $output .= theme('item_list', $item, t('Queues'));
  }
  return $output;
}

/**
 * Generate context sensitive breadcrumbs.
 *
 * @param $node
 *   A node object to use for context of the breadcrumbs.
 */
function hosting_set_breadcrumb($node) {
  $breadcrumbs[] = l(t('Home'), NULL);
  switch ($node->type) {
    case 'task':
      $breadcrumbs[] = _hosting_node_link($node->rid);
      break;
    case 'platform':
      $breadcrumbs[] = _hosting_node_link($node->web_server);
      break;
    case 'site':
      $breadcrumbs[] = _hosting_node_link($node->platform);
      break;
    case 'server':
      $breadcrumbs[] = l(t('Servers'), 'hosting/servers');
      break;
  }
  drupal_set_breadcrumb($breadcrumbs);
}

/**
 * Form to configure the frequency of queue execution.
 */
function hosting_queues_configure() {
  drupal_add_css(drupal_get_path('module', 'hosting') . '/hosting.css');
  $units = array(
    strtotime("1 second", 0) => t("Seconds"),
    strtotime("1 minute", 0) => t("Minutes"),
    strtotime("1 hour", 0) => t("Hours"),
    strtotime("1 day", 0) => t("Days"),
    strtotime("1 week", 0) => t("Weeks"),
  );
  $queues = hosting_get_queues();
  $form['#tree'] = TRUE;
  foreach ($queues as $queue => $info) {
    $form[$queue]['description'] = array(
      '#type' => 'item',
      '#value' => $info['name'],
      '#description' => $info['description'],
    );
    $form[$queue]["enabled"] = array(
      '#type' => 'checkbox',
      '#default_value' => $info['enabled'],
    );
    $form[$queue]["last_run"] = array(
      '#value' => hosting_format_interval(variable_get('hosting_queue_' . $queue . '_last_run', false)),
    );
    $form[$queue]['frequency']['#prefix'] = "<div class='hosting-queue-frequency'>";
    $form[$queue]['frequency']['#suffix'] = "</div>";
    if ($info['type'] == 'batch') {
      $form[$queue]['frequency']['items'] = array(
        '#value' => t('%count %items every', array(
          "%count" => $info['total_items'],
          "%items" => format_plural($info['total_items'], $info['singular'], $info['plural']),
        )),
        '#prefix' => "<div class='hosting-queue-frequency-items'>",
        '#suffix' => "</div>",
      );
    }
    else {
      $form[$queue]['frequency']['items'] = array(
        '#type' => 'textfield',
        '#size' => 3,
        '#maxlength' => 3,
        '#default_value' => $info['items'],
        '#suffix' => t(' %items every ', array(
          '%items' => $info['plural'],
        )),
        '#attributes' => array(
          'class' => 'hosting-select-frequency-items',
        ),
      );
    }
    foreach (array_reverse(array_keys($units)) as $length) {
      $unit = $units[$length];
      if (!($info['frequency'] % $length)) {
        $frequency_ticks = $info['frequency'] / $length;
        $frequency_length = $length;
        break;
      }
    }
    $form[$queue]['frequency']["ticks"] = array(
      '#type' => 'textfield',
      '#default_value' => $frequency_ticks,
      '#maxlength' => 5,
      '#size' => 5,
      '#attributes' => array(
        'class' => 'hosting-select-frequency-ticks',
      ),
    );
    $form[$queue]['frequency']["unit"] = array(
      '#type' => 'select',
      '#options' => $units,
      '#default_value' => $frequency_length,
      '#attributes' => array(
        'class' => 'hosting-select-frequency-unit',
      ),
    );
  }
  $form['help'] = array(
    '#type' => 'item',
    '#description' => t('Increasing the queue frequency to every 1 second will not cause them to be dispatched every second, as the dispatcher can only run once per minute via cron. However, with such a short frequency, executing the hosting-dispatch command manually from the CLI will allow you to \'force\' the queues to be dispatched between cron runs. This may be useful for development purposes.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save changes'),
  );
  return $form;
}

/**
 * Theme function to render the queue configuration form.
 *
 * @see hosting_queues_configure()
 */
function theme_hosting_queues_configure($form) {
  $queues = hosting_get_queues();
  $rows = array();
  $header = array(
    '',
    t('Description'),
    array(
      'data' => t('Frequency'),
      'class' => 'hosting-queue-frequency-head',
    ),
    t('Last run'),
  );
  foreach ($queues as $key => $info) {
    $row = array();
    $row[] = drupal_render($form[$key]['enabled']);
    $row[] = drupal_render($form[$key]['description']);
    $row[] = drupal_render($form[$key]['frequency']);
    $row[] = drupal_render($form[$key]['last_run']);
    $rows[] = $row;
  }
  $output = theme('table', $header, $rows);
  $output .= drupal_render($form['help']);
  $output .= drupal_render($form['submit']);
  $output .= drupal_render($form);
  return $output;
}

/**
 * Validation callback for the the queue execution frequency form.
 *
 * @see hosting_queues_configure()
 */
function hosting_queues_configure_validate($form, &$form_state) {
  foreach (hosting_get_queues() as $queue => $info) {
    if ($form_state['values'][$queue]) {
      if ($form_state['values'][$queue]['frequency']['ticks'] && !is_numeric($form_state['values'][$queue]['frequency']['ticks'])) {
        form_set_error($queue, t('Please enter a valid frequency.'));
      }
      if ($form_state['values'][$queue]['frequency']['items'] && !is_numeric($form_state['values'][$queue]['frequency']['items'])) {
        form_set_error($queue, t('Please enter a valid amount of items.'));
      }
    }
  }
}

/**
 * Submit callback for the the queue execution frequency form.
 *
 * @see hosting_queues_configure()
 */
function hosting_queues_configure_submit($form, &$form_state) {
  foreach (hosting_get_queues() as $queue => $info) {
    if ($form_state['values'][$queue]) {
      variable_set("hosting_queue_" . $queue . "_enabled", $form_state['values'][$queue]['enabled']);
      variable_set("hosting_queue_" . $queue . "_frequency", $form_state['values'][$queue]['frequency']['ticks'] * $form_state['values'][$queue]['frequency']['unit']);
      if ($info['type'] == 'serial') {
        variable_set("hosting_queue_" . $queue . "_items", $form_state['values'][$queue]['frequency']['items']);
      }
    }
  }
  drupal_set_message(t('The queue settings have been updated.'));
}

/**
 * Implementation of hook_form_alter().
 */
function hosting_form_alter(&$form, &$form_state, $form_id) {

  // Do not allow package or task nodes to be edited through the interface.
  if ($form_id == 'package_node_form' || $form_id == 'task_node_form') {
    drupal_access_denied();
    exit;
  }

  // Alter the 'Add User' form to remind users that this is not the New Client form
  if ($form_id == 'user_register') {
    $form[user_registration_help] = array(
      '#type' => 'item',
      '#description' => t('<strong>Adding a system user account does not make the user a Client that can add sites.</strong><br />
                     To add a Client, enable the Client feature and then add a new Client node.<br />
                     If you wish, you may then assign this system user to the Client as an \'Allowed user\' to inherit the permissions to add sites.'),
      '#weight' => '-10',
    );
  }

  // Remove additional UI added by core modules, that conflict with the hosting UI.
  $node_types = array(
    'site',
    'platform',
    'server',
    'client',
  );
  foreach ($node_types as $type) {
    if ($form_id == $type . '_node_form') {
      $form['options']['#access'] = FALSE;
      $form['menu']['#access'] = FALSE;
      $form['revision_information']['#access'] = FALSE;
      $form['author']['#access'] = FALSE;
      $form['comment_settings']['#access'] = FALSE;

      // because these aren't really posts, 'preview' doesnt make sense in this context.
      $form['buttons']['preview']['#access'] = FALSE;

      // Add message that verify is required for changes to take effect.
      if (in_array($type, array(
        'site',
        'platform',
        'server',
      ))) {
        $form['#submit'][] = 'hosting_form_verify_reqd_submit';
      }
    }
  }
}

/**
 * Submit handler to add a message that verify is required for changes to take effect.
 * See: https://drupal.org/node/1761932
 */
function hosting_form_verify_reqd_submit() {
  drupal_set_message('Any changes will take effect once the scheduled Verify task has been processed.');
}

/**
 * Hosting version of menu_rebuild() from Drupal 6.15
 * This is needed for the upgrade path of a hostmaster platform from 
 * 6.15 to 6.16. The Hosting module calls update hooks that run 
 * menu_rebuild() which break on a 6.16 platform as the semaphore 
 * table has not yet been introduced by system_update_6054() 
 * It is considered safe enough to ignore the new locking checks in 
 * 6.16's menu_rebuild() to get us past this step to successfully 
 * complete the hostmaster-migrate task.
 */
function hosting_menu_rebuild() {
  variable_del('menu_rebuild_needed');
  $menu = menu_router_build(TRUE);
  _menu_navigation_links_rebuild($menu);

  // Clear the menu, page and block caches.
  menu_cache_clear_all();
  _menu_clear_page_cache();
  if (defined('MAINTENANCE_MODE')) {
    variable_set('menu_rebuild_needed', TRUE);
  }
}

/**
 * Get the node associated with a given provision context.
 *
 * @param $name
 *   The name of the context to load.
 * @return
 *   Either the node object associated with the context, or FALSE if no
 *   associated node can be found.
 */
function hosting_context_load($name) {
  $name = ltrim($name, '@');

  // TODO : introduce static caching ?
  $result = db_query("SELECT nid FROM {hosting_context} WHERE name='%s'", $name);
  if ($obj = db_fetch_object($result)) {
    return node_load($obj->nid);
  }
  return FALSE;
}

/**
 * Add a node to the context lookup db.
 *
 * @param $nid
 *   The nid of the node to associate to the given provision context name.
 * @param $name
 *   The name of the provision context.
 */
function hosting_context_register($nid, $name) {

  // Check first to see if this nid exists in the system. If so, update its name
  $result = db_query("SELECT nid FROM {hosting_context} WHERE nid=%d", $nid);
  if ($obj = db_fetch_object($result)) {
    db_query("UPDATE {hosting_context} SET name = '%s' WHERE nid = %d", $name, $nid);
  }
  else {

    // It's a new item
    db_query("INSERT INTO {hosting_context} (nid, name) VALUES (%d, '%s')", $nid, $name);
  }

  // We include the file instead of enabling the module,
  // because we do not want the overhead of having all the
  // path UI stuff on nodes.
  require_once "./modules/path/path.module";

  // Delete an existing alias, could be left behind after migrating a site from this name.
  path_set_alias(NULL, "hosting/c/{$name}");

  // Add the new alias.
  path_set_alias("node/{$nid}", "hosting/c/{$name}");
}

/**
 * Delete an alias from the context lookup table.
 *
 * @param $nid
 *   The nid of the node to remove the association from.
 */
function hosting_context_delete($nid) {
  db_query("DELETE FROM {url_alias} WHERE src='node/%d'", $nid);
  db_query("DELETE FROM {hosting_context} WHERE nid=%d", $nid);
}

/**
 * Return the hosting context name for a node.
 *
 * @param $nid
 *   The nid of the node to get the provision context name for.
 * @return
 *   The provision context name associated with the specified node, will be
 *   prefixed with '@' or FALSE if node doesnt exist.
 */
function hosting_context_name($nid) {
  $node = node_load($nid);
  if (!$node) {
    drupal_set_message(t('Error: cannot load node id %nid to find its context', array(
      '%nid' => $nid,
    )), 'error');
    return FALSE;
  }
  return '@' . $node->hosting_name;
}

/**
 * Define the node types that have associated provision contexts.
 */
function hosting_context_node_types() {
  return array(
    'site',
    'platform',
    'server',
  );
}

/**
 * Implementation of hook_views_api().
 */
function hosting_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'hosting'),
  );
}

/**
 * General settings form.
 */
function hosting_settings() {
  $form['settings'] = array(
    '#type' => 'item',
    '#title' => t('General Hosting settings'),
    '#value' => t('Here you may set various general settings that apply to site management or to the frontend system.'),
    '#weight' => 0,
  );
  $profiles = array();
  foreach (hosting_get_profiles() as $id => $name) {
    $profile = hosting_package_instance_load(array(
      'p.nid' => $id,
    ));
    $profiles[$profile->short_name] = theme('hosting_site_profile', $profile);

    // Don't allow a site to be provisioned with hostslave or hostmaster profile
    if (in_array($name, array(
      'Hostslave',
      'Hostmaster',
    ))) {
      unset($profiles[$profile->short_name]);
    }
  }
  natcasesort($profiles);
  $default_value = hosting_package_instance_load(array(
    'p.nid' => hosting_get_default_profile(),
  ))->short_name;
  $form['hosting_default_profile'] = array(
    '#type' => 'select',
    '#title' => t('Default profile'),
    '#options' => $profiles,
    '#description' => t('The default install profile to use when creating new sites.'),
    '#default_value' => $default_value,
  );
  $form['hosting_ignore_default_profiles'] = array(
    '#type' => 'checkbox',
    '#title' => t('Hide platforms with non-default profiles'),
    '#description' => t('When selecting a Drupal core profile, hide platforms that also contain other profiles (i.e intended as distributions). Warning: requires a platform other than the Hostmaster platform.'),
    '#default_value' => variable_get('hosting_ignore_default_profiles', FALSE),
  );
  $form['hosting_lock_platforms_by_default'] = array(
    '#type' => 'checkbox',
    '#title' => t('Lock platforms by default on initial creation'),
    '#description' => t('When verifying a new platform, set its initial state to \'Locked\'. This can avoid having sites created on a new platform before there has been a chance to test it. Those users with the \'create sites on locked platforms\' permission (e.g., with the \'platform manager\' role) will still be able to build sites.'),
    '#default_value' => variable_get('hosting_lock_platforms_by_default', FALSE),
  );
  $form['hosting_require_disable_before_delete'] = array(
    '#type' => 'checkbox',
    '#title' => t('Require site to be disabled before deletion'),
    '#description' => t('Check if you wish for users to run the Disable task on a site prior to running the Delete task.'),
    '#default_value' => variable_get('hosting_require_disable_before_delete', TRUE),
  );
  $form['hosting_welcome_page'] = array(
    '#type' => 'checkbox',
    '#title' => t('Redirect anonymous users to the Welcome page'),
    '#description' => t('By default, anonymous users will receive a 403 error, since they don\'t have the \'access content\' permission. This option will redirect them to the !welcome_page.', array(
      '!welcome_page' => l('Welcome page', 'welcome'),
    )),
    '#default_value' => variable_get('hosting_welcome_page', TRUE),
  );
  if (hosting_feature('cron')) {
    $form['hosting_cron_use_backend'] = array(
      '#type' => 'radios',
      '#title' => t('Cron method'),
      '#description' => t('For running cron on a site. You can use the drush cron implementation or the web-based cron.php method using builtin HTTP requests. The drush implementation is more reliable but will be slower than the web-based approach if the webserver has an opcode cache (like APC) configured.'),
      '#options' => array(
        'Web-based',
        'Drush',
      ),
      '#default_value' => variable_get('hosting_cron_use_backend', TRUE),
    );
  }
  return system_settings_form($form);
}

Functions

Namesort descending Description
drush_hosting_setup Initial hosting setup drush command.
hosting_block Implementation of hook_block().
hosting_context_delete Delete an alias from the context lookup table.
hosting_context_load Get the node associated with a given provision context.
hosting_context_name Return the hosting context name for a node.
hosting_context_node_types Define the node types that have associated provision contexts.
hosting_context_register Add a node to the context lookup db.
hosting_disabled_site Page callback for a site that has been disabled.
hosting_domain_allowed Check site URL is allowed.
hosting_form_alter Implementation of hook_form_alter().
hosting_form_verify_reqd_submit Submit handler to add a message that verify is required for changes to take effect. See: https://drupal.org/node/1761932
hosting_init Implementation of hook_init().
hosting_js_page Page callback for a page to be rendered in modal frame.
hosting_menu Implementation of hook_menu().
hosting_menu_access Menu access callback for creating a node provided by a Hosting feature.
hosting_menu_alter Implementation of hook_menu_alter().
hosting_menu_rebuild Hosting version of menu_rebuild() from Drupal 6.15 This is needed for the upgrade path of a hostmaster platform from 6.15 to 6.16. The Hosting module calls update hooks that run menu_rebuild() which break on a 6.16 platform as the semaphore table…
hosting_nodeapi Implementation of hook_nodeapi().
hosting_node_help Per node type description text. To be stored in the node_type table.
hosting_perm Implementation of hook_perm().
hosting_queues List queues or tasks in a queue if a key is provided.
hosting_queues_configure Form to configure the frequency of queue execution.
hosting_queues_configure_submit Submit callback for the the queue execution frequency form.
hosting_queues_configure_validate Validation callback for the the queue execution frequency form.
hosting_queue_summary_block Build a block summarising the hosting queues.
hosting_settings General settings form.
hosting_set_breadcrumb Generate context sensitive breadcrumbs.
hosting_site_maintenance Page callback for a site that is undergoing maintenance.
hosting_theme Implementation of hook_theme().
hosting_views_api Implementation of hook_views_api().
hosting_welcome Provide a welcome page for anonymous users.
theme_hosting_queues_configure Theme function to render the queue configuration form.
_hosting_node_add Replacement node/add page.
_hosting_node_link
_hosting_setup_cron Set up the hosting-dispatch command in the aegir user's crontab.

Constants

Namesort descending Description
HOSTING_MAX_ALIAS_LENGTH The maximum length for a site alias. Leaving room to append '.alias.drushrc.php' and keep filenames below 255.