'admin/hosting', 'title' => t('Hosting'), 'description' => t('Configure and manage the hosting system'), 'callback' => 'drupal_get_form', 'callback arguments' => array('hosting_features_form'), 'access' => user_access('administer hosting'), 'type' => MENU_NORMAL_ITEM ); $items[] = array( 'path' => 'admin/hosting/features', 'title' => t('Features'), 'description' => t('Configure the exposed functionality of the Hosting system'), 'weight' => -100, 'callback' => 'drupal_get_form', 'callback arguments' => array('hosting_features_form'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'access' => user_access('administer hosting features'), ); $items[] = array( 'path' => 'admin/hosting/queues', 'title' => t('Queues'), 'description' => t('Configure the frequency that cron, backup and task events are process'), 'callback' => 'drupal_get_form', 'callback arguments' => array('hosting_queues_configure'), 'type' => MENU_LOCAL_TASK, 'access' => user_access('administer hosting queues'), ); // Kludge : Replace node/add page to hide functionality. $items[] = array('path' => 'node/add', 'title' => t('Create content'), 'callback' => '_hosting_node_add', 'access' => user_access('access content'), 'type' => MENU_ITEM_GROUPING, 'weight' => 1); // Disable unsupported features. $features = module_invoke_all('hosting_features'); $types = node_get_types(); foreach ($features as $type => $feature) { if (isset($feature['node'])) { $type_url_str = str_replace('_', '-', $feature['node']); $items[] = array( 'path' => 'node/add/'. $type_url_str, 'title' => drupal_ucfirst($types[$feature['node']]->name), 'access' => (($user->uid == 1) || node_access('create', $feature['node'])) && (hosting_feature($type) !== HOSTING_FEATURE_DISABLED), ); } } // Queue page & task lists $items[] = array( 'path' => 'hosting/queues', 'callback' => 'hosting_queues', 'type' => MENU_CALLBACK, 'access' => user_access('access task logs') ); } return $items; } /** * 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. */ function hosting_nodeapi(&$node, $op, $teaser) { $func = "hosting_nodeapi_" . $node->type . "_" . str_replace(" ", "_", $op); if (function_exists($func)) { $func($node, $op, $teaser); } } function hosting_drush_command() { $items['hosting dispatch'] = array( 'callback' => 'hosting_dispatch', 'description' => 'Centralized command for dispatching the various queue processors (hosting, cron, backup etc.)' ); $items['hosting setup'] = array( 'callback' => 'hosting_setup', 'description' => t('Set up initial configuration settings such as the cron entry for the queue dispatcher and more.'), ); $queues = hosting_get_queues(); foreach ($queues as $queue => $info) { $dispatch = t("Dispatched: @items items every @frequency minutes", array('@items' => $info['items'], '@frequency' => round($info['frequency'] / 60))); $items['hosting ' . $queue] = array( 'callback' => 'hosting_run_queue', 'description' => $info['description'] . " " . $dispatch ); } return $items; } define('HOSTING_FEATURE_DISABLED', 0); define('HOSTING_FEATURE_ENABLED', 1); define('HOSTING_FEATURE_REQUIRED', 2); function hosting_feature($feature) { static $features = array(); if (!sizeof($features)) { $features = module_invoke_all("hosting_features"); } return variable_get('hosting_feature_' . $feature, $features[$feature]['status']); } /** * Implementation of hook_form_alter */ function hosting_form_alter($form_id, &$form) { // Replace configuration form submit handler if ($form_id == 'provision_configure') { $form['#submit'] = array('hosting_configure_provision' => array()); } } /** * Implementation of hook_perm */ function hosting_perm() { return array('access hosting wizard', 'administer hosting queues', 'administer hosting features', 'administer hosting'); } /** * Implementation of hook_init */ function hosting_init() { drupal_add_css(drupal_get_path('module', 'hosting') . '/hosting.css'); // 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)); define('HOSTING_DEFAULT_PLATFORM', variable_get('hosting_default_platform', 6)); 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)); } /** * Replacement submit handler for the admin/settings/provision page * * This submit handler saves the settings to the current provision environments' * individual nodes. These nodes intelligently save the settings provision uses * in it's environment. */ function hosting_configure_provision($form_id, &$values) { $platform = node_load(HOSTING_OWN_PLATFORM); $platform->revision = true; $platform->publish_path = $_SERVER['DOCUMENT_ROOT']; node_save($platform); $db_server = node_load(HOSTING_OWN_DB_SERVER); $db_server->revision = true; $db_server->title = $values['db_server']['db_host']; $db_server->db_user = $values['db_server']['db_user']; $db_server->db_type = $values['db_server']['db_type']; if ($values['db_server']['db_passwd']) { $db_server->db_passwd = $values['db_server']['db_passwd']; } node_save($db_server); $web_server = node_load($platform->web_server); $web_server->revision = true; $web_server->config_path = $values['web_server']['config_path']; $web_server->backup_path = $values['web_server']['backup_path']; $web_server->script_user = $values['web_server']['script_user']; $web_server->restart_cmd = $values['web_server']['restart_cmd']; $web_server->web_group = $values['web_server']['web_group']; node_save($web_server); } /** * Maps the properties of a node object to an associative array, based on node type, incrementally. * * The back end provisioning system accepts communication via a set of pre-defined arguments. * This function will generate whatever information can be extracted from a specific task, * regardless what node type it is related to, and use this for generating the command to be executed. * * This functionality is similar in scope to the token module, but the token module contains much additional * complexity that was not necessary for us, and provided a lot of information that we did not need. * * @param node * A node id, or node of any type defined by the hosting system. * Will call hook_hosting_map_values_$node->type to provide actual mappings. * @return * An associative array of terms to be used on the command line, or in templates. */ function hosting_map_values($node) { if (is_numeric($node)) { $node = node_load($node); } $values = array(); $func = 'hosting_map_values_' . $node->type; if (function_exists($func)) { $values = $func($node); } return $values; } function _hosting_node_link($nid) { $node = node_load($nid); if ($node->nid) { return l(filter_xss($node->title), "node/" . $node->nid); } } 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'] = array('info' => t('Hosting queues'), 'enabled' => 1, 'region' => 'right', 'weight' => 0); $blocks['hosting_queues_summary'] = array('info' => t('Hosting queues summary'), 'enabled' => 1, 'region' => 'right', 'weight' => 1); return $blocks; case 'view' : switch ($delta) { case 'hosting_summary': return array( 'title' => t('Summary'), 'content' => hosting_summary_block()); break; case 'hosting_queues': return array('title' => t('Queues'), 'content' => hosting_queue_block()); break; case 'hosting_queues_summary': return array('title' => t('Queues summary'), 'content' => hosting_queue_summary_block()); break; } } } 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']); /* to print all stats of the queue... */ #$lambda = create_function('$k,$v', 'return "$k: $v";'); #$output .= theme('item_list', array_map($lambda, array_keys($info), $info), $info['name']); } return $output; } } function hosting_queue_block() { if (user_access('access task logs')) { $queues = hosting_get_queues(); $output = ''; foreach ($queues as $queue => $info) { $func = 'hosting_'.$info['singular'].'_summary'; if (function_exists($func)) { $output .= $func(); } } return $output; } } function hosting_summary_block() { $db_servers = _hosting_get_db_servers(); $platforms = _hosting_get_platforms(); $web_servers = _hosting_get_web_servers(); return theme('hosting_summary_block', array_map('_hosting_node_link', array_keys($platforms)), array_map('_hosting_node_link', array_keys($web_servers)), array_map('_hosting_node_link', array_keys($db_servers))); } function theme_hosting_summary_block($platforms, $web_servers, $db_servers) { $output .= theme("item_list", $platforms, t('Platforms')); $output .= theme("item_list", $web_servers, t('Web servers')); $output .= theme("item_list", $db_servers, t('Database servers')); return $output; } /** * Check if a hostname provided is an ip address */ function _hosting_valid_ip($hostname) { //TODO : provide IPv6 support $parts = explode('.', $hostname); if (sizeof($parts) != 4) { return false; } foreach ($parts as $part) { if (((int) $part < 0) || ((int) $part > 255)) { return false; } } return true; } function hosting_format_interval($ts) { if ($ts==mktime()) { return t('Now'); } if (!$ts) { //Treats EPOCH as never return t('Never'); } return t("@interval ago", array("@interval" => format_interval(mktime() - $ts, 1))); } function hosting_setup() { variable_set('hosting_dispatch_enabled', FALSE); if (_provision_setup()) { ## attempting to run the dispatch command, to make sure it runs without the queue being ## enabled. variable_set('hosting_dispatch_enabled', TRUE); exec(_hosting_dispatch_cmd(), $return, $code); variable_set('hosting_dispatch_enabled', FALSE); $return = join("\n", $return); $data = unserialize($return); if ($code == PROVISION_SUCCESS) { variable_set('hosting_dispatch_enabled', TRUE); provision_log("message", t("Dispatch command was run successfully")); _hosting_setup_cron(); } else { provision_log("error", t("Dispatch command could not be run. Returned: \n@return", array('@return' => $return))); provision_set_error(PROVISION_FRAMEWORK_ERROR); } } // @TODO use provision_output for this, but we need pretty print first. $logs = provision_get_log(); foreach ($logs as $log) { print "$log[message]\n"; } if (provision_get_error()) { print "\nThe command did not complete successfully, please fix the issues and re-run this script."; } } function _hosting_setup_cron() { $newcron = array(); $existing = FALSE; exec('crontab -l 2> /dev/null', $oldcron); if (sizeof($oldcron)) { foreach ($oldcron as $line => $entry) { if (preg_match('/hosting dispatch/', $entry)) { provision_log("Notice", t("Existing hosting dispatch cron entry was found. Not replacing")); $existing = TRUE; break; } } } else { provision_log("message", t("No existing crontab was found")); } if (!$existing) { if (sizeof($oldcron)) { $newcron = $oldcron; variable_set('hosting_cron_backup', $oldcron); } $newcron[] = hosting_queues_cron_cmd(); $tmpnam = tempnam('hostmaster', 'hm.cron'); $fp = fopen($tmpnam, "w"); foreach ($newcron as $line) { fwrite($fp, $line . "\n"); } fclose($fp); system(sprintf('crontab %s', escapeshellarg($tmpnam))); unlink($tmpnam); provision_log("Notice", t("Installed hosting dispatch cron entry to run every minute")); } } /** * 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]) && node_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') && node_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 = '
'. l(drupal_ucfirst($type->name), "node/add/$type_url_str", array('title' => $title)) .'
'; $out .= '
'. filter_xss_admin($type->description) .'
'; $item[$type->name] = $out; } } if (isset($item)) { uksort($item, 'strnatcasecmp'); $output = t('Choose the appropriate item from the list:') .'
'. implode('', $item) .'
'; } else { $output = t('No content types available.'); } } return $output; } /** * List queues or tasks in a queue if a key is provided */ function hosting_queues($key='') { $queues = hosting_get_queues(); if ($queues[$key]) { if ($queues[$key]['name']) { $output .= "

".$queues[$key]['name']."

"; } $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 */ 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 'package_release' : $breadcrumbs[] = _hosting_node_link($node->package); break; } drupal_set_breadcrumb($breadcrumbs); } function hosting_log($type, $message, $severity = 0) { if (DRUSH_VERBOSE) print "$message\n"; if ($type) watchdog($type, $message, $severity); } function hosting_features_form() { $form['features'] = array( '#type' => 'item', '#title' => t('Optional system features'), '#value' => t('You may choose any of the additional system features from the list below.'), '#description' => t('Features marked experimental have not been completed to a satisfactory level to be considered production ready, so use at your own risk.'), ); $features = module_invoke_all('hosting_feature'); # $features = hosting_get_features(TRUE); $form['features']['#tree'] = TRUE; foreach ($features as $feature => $info) { $form['features'][$feature] = array( '#type' => 'checkbox', '#title' => $info['title'], '#description' => $info['description'], '#default_value' => hosting_feature($feature), '#required' => hosting_feature($feature) == HOSTING_FEATURE_REQUIRED, ); } return system_settings_form($form); } function hosting_features_form_submit($form_id, $values) { foreach ($values['features'] as $feature => $value) { variable_set('hosting_feature_' . $feature, $value); } menu_rebuild(); } function hosting_get_features($refresh = FALSE) { $cache = cache_get('hosting_features'); if (!$cache->data || $refresh) { ## include any optional hosting.feature.*.inc files $files = drupal_system_listing("hosting\.feature\.[a-zA-Z_]*\.inc$", "modules"); if (sizeof($files)) { foreach ($files as $name => $info) { include_once($info->filename); } } $functions = get_defined_functions(); foreach ($functions['user'] as $function) { if (preg_match('/_hosting_feature$/', $function)) { $hooks[] = $function; } } $features = array(); foreach ($hooks as $func) { $features = array_merge($features, $func()); } cache_set('hosting_features', 'cache', serialize($features)); return $features; } else { return unserialize($cache->data); } }