You are here

drd_server.module in Drupal Remote Dashboard Server 7.2

Same filename and directory in other branches
  1. 6.2 drd_server.module
  2. 6 drd_server.module
  3. 7 drd_server.module

Provides XMLRPC implementation to respond to requests from DRD.

File

drd_server.module
View source
<?php

/**
 * @file
 * Provides XMLRPC implementation to respond to requests from DRD.
 */
define('DRD_SERVER_API_VERSION', '2.4.0');
define('DRD_SERVER_ERROR_WRONG_API', 1001);
define('DRD_SERVER_ERROR_WRONG_REFERER', 1002);
define('DRD_SERVER_ERROR_NO_REFERER', 1003);
define('DRD_SERVER_ERROR_NO_OP', 1004);
define('DRD_SERVER_ERROR_NO_FUNC', 1005);
define('DRD_SERVER_ERROR_NO_LOG_PHP', 1006);
define('DRD_SERVER_ERROR_MISSING_AES', 1007);
define('DRD_SERVER_ERROR_WRONG_KEYS', 1999);
include 'drd_server.aes.inc';

/** ======================================================================
 *
 * Hooks
 */

/**
 * Implementation of hook_xmlrpc().
 *
 * This function provides Drupal with an array to map XML-RPC callbacks to the
 * functions implemented by this module.
 *
 * @return array
 *   An array of key/value pairs where key is the action name and value the corresponding function.
 */
function drd_server_xmlrpc() {
  include_once 'drd_server.domain.inc';
  include_once 'drd_server.server.inc';
  return array(
    'drd.key' => 'drd_server_key',
    'drd.execute' => 'drd_server_execute',
    'drd.status' => 'drd_server_status',
    'drd.heartbeat' => 'drd_server_heartbeat',
    'drd.config.server.read' => 'drd_server_config_server_read',
    'drd.config.server.save' => 'drd_server_config_server_save',
    'drd.config.domain.read' => 'drd_server_config_domain_read',
    'drd.config.domain.save' => 'drd_server_config_domain_save',
    'drd.retrieve.blocks' => 'drd_server_render_blocks',
  );
}

/**
 * Implements hook_menu_site_status_alter().
 */
function drd_server_menu_site_status_alter(&$menu_site_status, $path) {
  if (strpos($path, 'admin/drd_server/') !== FALSE) {
    $menu_site_status = MENU_SITE_ONLINE;
  }
}

/**
 * Implements hook_drd_server_actions().
 *
 * @return array
 *   An array of keyed action definitions.
 */
function drd_server_drd_server_actions() {
  return array(
    'drd.server.install' => array(
      'category' => t('Domain'),
      'label' => t('DRD: remotely install/uninstall'),
      'callback' => 'drd_install_drd_server',
      'mode' => 'domain',
      'remote' => FALSE,
      'queue' => TRUE,
      'fields' => array(
        'attention' => array(
          '#markup' => t('ATTENTION: This will flush all caches on your remote site, so please be aware and use this with care, especially if you have a lot of traffic on your remote site.'),
        ),
        'reset' => array(
          '#type' => 'checkbox',
          '#title' => t('Install'),
          '#default_value' => TRUE,
        ),
      ),
    ),
    'drd.server.domains' => array(
      'category' => t('Server'),
      'label' => t('Update server domains'),
      'callback' => 'drd_server_server_domains',
      'mode' => 'server',
      'remote' => TRUE,
      'queue' => TRUE,
      'follower' => array(
        'a' => 'drd.actions',
      ),
    ),
    'drd.info' => array(
      'label' => t('Update info'),
      'callback' => 'drd_server_domain_info',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'refresh' => TRUE,
    ),
    'drd.cache.flush' => array(
      'label' => t('Flush Cache'),
      'callback' => 'drd_server_domain_flush_cache',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
    ),
    'drd.run.cron' => array(
      'label' => t('Run Cron'),
      'callback' => 'drd_server_domain_run_cron',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'follower' => array(
        'a' => 'drd.info',
      ),
    ),
    'drd.switch.maintenance' => array(
      'label' => t('Maintenance Mode'),
      'callback' => 'drd_server_domain_switch_maintenance',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'fields' => array(
        'maintenancemode' => array(
          '#type' => 'checkbox',
          '#title' => t('On'),
          '#default_value' => TRUE,
        ),
      ),
    ),
    'drd.list.updates' => array(
      'label' => t('List new modules and themes'),
      'callback' => 'drd_server_domain_list_updates',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'follower' => array(
        'a' => 'drd.info',
      ),
    ),
    'drd.server.list.updates' => array(
      'label' => t('List new modules and themes (including disabled)'),
      'callback' => 'drd_server_server_list_updates',
      'mode' => 'server',
      'remote' => TRUE,
      'queue' => TRUE,
    ),
    'drd.run.update' => array(
      'label' => t('Run Database Updates'),
      'callback' => 'drd_server_domain_run_update',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'follower' => array(
        'a' => 'drd.info',
      ),
    ),
    'drd.update.translation' => array(
      'label' => t('Update Translations'),
      'callback' => 'drd_server_domain_update_translation',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'follower' => array(
        'a' => 'drd.info',
      ),
    ),
    'drd.server.php.error.log' => array(
      'category' => t('Server'),
      'label' => t('PHP Error Log'),
      'callback' => 'drd_server_server_php_error_log',
      'mode' => 'server',
      'remote' => TRUE,
      'queue' => TRUE,
    ),
    'drd.set.user1' => array(
      'label' => t('Reset User 1 Credentials'),
      'callback' => 'drd_server_domain_reset_user1',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => FALSE,
      'fields' => array(
        'username' => array(
          '#type' => 'textfield',
          '#title' => t('Username'),
          '#default_value' => '',
        ),
        'userpass' => array(
          '#type' => 'password',
          '#title' => t('Password'),
          '#default_value' => '',
        ),
        'userpassrepeat' => array(
          '#type' => 'password',
          '#title' => t('Repeat password'),
          '#default_value' => '',
        ),
      ),
    ),
    'drd.php' => array(
      'label' => t('Execute PHP code'),
      'callback' => 'drd_server_domain_php',
      'mode' => 'domain',
      'remote' => TRUE,
      'queue' => TRUE,
      'fields' => array(
        'php' => array(
          '#type' => 'textarea',
          '#title' => t('PHP-Code'),
          '#default_value' => '',
          '#description' => t('The PHP code to be executed, wrapped in php tags. Note: The PHP module needs to be enabled on the remote domain.'),
        ),
      ),
    ),
  );
}

/**
 * Implements hook_block_info().
 *
 * @return array
 *   An array of all blocks defined by drd_server.
 */
function drd_server_block_info() {
  $blocks = array();
  if (module_exists('admin')) {
    $blocks['drd'] = array(
      'info' => t('Admin Extras Dashboard'),
      'cache' => DRUPAL_CACHE_PER_ROLE,
      'admin' => TRUE,
    );
  }
  return $blocks;
}

/**
 * Implements hook_block_view().
 *
 * @param string $delta
 *   The string defining the block that should be received.
 *
 * @return mixed
 *   Either a renderable array for the block referenced by delta or FALSE if that block is either empty or the current
 *   user has no permission to access that block.
 */
function drd_server_block_view($delta) {
  switch ($delta) {
    case 'drd':
      module_load_include('inc', 'drd_server', 'drd_server.admin');
      return drd_server_admin_block();
  }
  return FALSE;
}

/**
 * Implements hook_navbar().
 */
function drd_server_navbar() {
  $items = array();
  module_load_include('inc', 'drd_server', 'drd_server.admin');
  $items['drd_server'] = array(
    '#type' => 'navbar_item',
    'tab' => array(
      '#type' => 'link',
      '#title' => t('Extra Admin'),
      '#href' => 'admin',
      '#options' => array(
        'attributes' => array(
          'title' => t('Extra Admin'),
          'class' => array(
            'navbar-icon',
            'navbar-icon-configuration',
          ),
        ),
      ),
    ),
    'tray' => array(
      '#heading' => t('Extra Admin'),
      'extra_admin' => array(
        '#theme' => 'item_list',
        '#items' => drd_server_admin_menu(),
        '#attributes' => array(
          'class' => array(
            'drd-server-extra-admin',
            'menu',
            'nav',
            'navbar-menu-root',
            'navbar-root',
          ),
        ),
      ),
    ),
    '#weight' => 150,
  );
  drupal_add_css(drupal_get_path('module', 'drd_server') . '/drd_server.css');
  return $items;
}

/**
 * Implements hook_permission().
 *
 * @return array
 *   An array of defined permissions by this module.
 */
function drd_server_permission() {
  return array(
    'flush cache' => array(
      'title' => t('Flush Cache'),
    ),
  );
}

/**
 * Implements hook_menu().
 *
 * @return array
 *   An array of all defined menu items by this module.
 */
function drd_server_menu() {
  $items['admin/drd_server/%'] = array(
    'title' => 'Check if DRD Server is installed',
    'page callback' => 'drd_server_check',
    'page arguments' => array(
      2,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/system/drd_settings'] = array(
    'title' => 'DRD Remote Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'drd_server_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'drd_server.admin.inc',
  );
  $items['admin/config/system/drd_settings/%'] = array(
    'title' => 'DRD Remote Settings: Keys',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'drd_server_settings_keys',
      4,
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'drd_server.admin.inc',
  );
  $items['admin/drd_server/flush/cache'] = array(
    'title' => 'Flush Cache',
    'page callback' => 'drd_server_domain_flush_cache',
    'page arguments' => array(
      TRUE,
    ),
    'access arguments' => array(
      'flush cache',
    ),
    'file' => 'drd_server.domain.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/drd_server/rebuild_scheduler'] = array(
    'title' => 'Rebuild Scheduler',
    'page callback' => 'drd_server_domain_rebuild_scheduler',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'drd_server.domain.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/drd_server/update/translation'] = array(
    'title' => 'Update translation',
    'page callback' => 'drd_server_admin_update_translation',
    'access arguments' => array(
      'translate interface',
    ),
    'file' => 'drd_server.admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_drd_config_domain().
 *
 * @return array
 *   Form definition for domain settings.
 */
function drd_server_drd_config_domain() {
  $form = array();
  if (!module_exists('block')) {
    $form['drd_server_blocks'] = array(
      '#type' => 'checkboxes',
      '#options' => array(),
      '#disabled' => TRUE,
      '#title' => t('Blocks to display (unavailable)'),
      '#description' => t('Block module disabled on the remote server.'),
    );
    return $form;
  }
  $block_list = array();
  foreach (module_implements('block_info') as $module) {
    foreach (module_invoke($module, 'block_info') as $key => $block) {
      $block_list[$module . ':' . $key] = $block['info'];
    }
  }
  $form['drd_server_blocks'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Blocks to display'),
    '#options' => $block_list,
    '#default_value' => variable_get('drd_server_blocks', array()),
  );
  return $form;
}

/**
 * Implements hook_file_download().
 */
function drd_server_file_download($uri) {
  if (strpos(file_uri_target($uri), 'drd_server_debug_') === 0) {
    if (user_access('administer site configuration')) {
      return array(
        'Pragma' => 'public',
        'Expires' => 0,
        'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
        'Content-Type' => 'application/force-download',
      );
    }
  }
  return NULL;
}

/** ======================================================================
 *
 * Remote client functions
 */

/**
 * This is called to update the excryption keys for this server and all it's
 * domains hosted in the same Drupal installation.
 *
 * @param string $api
 *   The API used by DRD, is used to make sure that APIs from DRD and DRD_Server are compatible.
 * @param int $timestamp
 *   Not used yet. Could be used to make sure that the request is not too old - to avoid hacking attacks. However,
 *   security by AES is sufficient and as timestamp is going to change for each valid request from DRD, the encrypted
 *   request will change as well all the time, so that a brute force attack is very unlikely.
 * @param string $langcode
 *   Language used by the current user in DRD so that messages from here can be properly localized.
 * @param boolean $debug
 *   TRUE if DRD is in debug mode so that more verbose messages get generated.
 * @param string $domainurl
 *   The domain name for which AES keys should be set.
 * @param string $aes_key
 *   The AES key to be set.
 * @param string $aes_cipher
 *   The AES Cipher to be set.
 * @param string $aes_iv
 *   The AES Initial Vector to be set.
 * @param string $aes_impl
 *   The AES Implementation to be set.
 * @param string $cluster_token
 *   The token to be used if in cluster mode.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_key($api, $timestamp, $langcode, $debug, $domainurl, $aes_key = '', $aes_cipher = '', $aes_iv = '', $aes_impl = '', $cluster_token = '') {
  if ($timestamp != REQUEST_TIME) {

    // We could verify that the request is current.
  }
  global $language;
  $language->language = $langcode;
  _drd_server_debug_mode($debug);
  _drd_server_watchdog('AES key change request.');
  $allowed = variable_get('drd_allowed_referer', '');
  $referer = empty($cluster_token) ? ip_address() : $cluster_token;
  if (empty($allowed)) {
    return drd_server_error(t('Referer (%referer) not allowed, nothing configured yet.', array(
      '%referer' => $referer,
    )), DRD_SERVER_ERROR_NO_REFERER);
  }
  if (strpos($allowed, $referer) === FALSE) {
    _drd_server_watchdog('AES key change request unauthorized.', array(), WATCHDOG_ALERT);
    return drd_server_error(t('Referer (%referer) not allowed.', array(
      '%referer' => $referer,
    )), DRD_SERVER_ERROR_WRONG_REFERER);
  }
  $aes_keys = variable_get('drd_aes_keys', array());
  if (!empty($cluster_token)) {
    if (empty($aes_keys[$cluster_token]['cluster_mode']) || empty($aes_keys[$cluster_token]['cluster_ips']) || strpos($aes_keys[$cluster_token]['cluster_ips'], ip_address()) === FALSE) {
      _drd_server_watchdog('AES key change request unauthorized due to ip address mismatch.', array(), WATCHDOG_ALERT);
      return drd_server_error(t('Referer (%referer) not allowed from %address.', array(
        '%referer' => $referer,
        '%address' => ip_address(),
      )), DRD_SERVER_ERROR_WRONG_REFERER);
    }
  }
  if ($api !== DRD_SERVER_API_VERSION) {
    _drd_server_watchdog('Wrong API: %api.', array(
      '%api' => $api,
    ), WATCHDOG_ALERT);
    return drd_server_error(t('Wrong API.'), DRD_SERVER_ERROR_WRONG_API);
  }
  $sites = drd_server_read_sites();
  if (empty($domainurl)) {
    $aes_keys[$referer] = array(
      'key' => $aes_key,
      'cipher' => $aes_cipher,
      'iv' => $aes_iv,
      'impl' => $aes_impl,
      'cluster_mode' => !empty($cluster_token),
      'cluster_ips' => empty($aes_keys[$cluster_token]['cluster_ips']) ? ip_address() : $aes_keys[$cluster_token]['cluster_ips'],
    );
    variable_set('drd_aes_keys', $aes_keys);
    $domainurls = $sites;
  }
  else {
    $domainurls = array(
      $domainurl => $sites[$domainurl],
    );
  }
  drd_server_key_remote($domainurls, $aes_keys);
  return drd_server_result('drd.key', TRUE);
}

/**
 * This is called to execute one of the actions that are defined on one of the
 * hook_drd_server_actions().
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_execute() {
  $args = func_get_args();
  $action_name = array_shift($args);
  $valid = _drd_server_validate_request($args, $action_name);
  if ($valid !== TRUE) {
    return $valid;
  }
  $action_name = array_pop($args);
  $actions = module_invoke_all('drd_server_actions');
  if ($action_name == 'drd.actions') {
    return drd_server_result('drd.actions', $actions);
  }
  if (!isset($actions[$action_name])) {
    return drd_server_error(t('Action %action_name does not exist.', array(
      '%action_name' => $action_name,
    )), DRD_SERVER_ERROR_NO_OP);
  }
  $action = $actions[$action_name];
  if (!function_exists($action['callback'])) {
    return drd_server_error(t('Action %action_name does not have any callback.', array(
      '%action_name' => $action_name,
    )), DRD_SERVER_ERROR_NO_FUNC);
  }
  return call_user_func_array($action['callback'], $args);
}

/**
 * Callback to retrieve current status of the domain.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_status() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  if (module_exists('update')) {
    module_load_include('inc', 'update', 'update.compare');
    $projects = array();
    foreach (update_get_projects() as $project_name => $full_info) {
      $projects[$project_name] = $full_info['info']['version'];
    }
  }
  else {

    // If for some reason we don't have update module available to check,
    // fall back to the old way of trying to use module names, which still
    // works far more often than not.
    $projects = db_select('system', 's')
      ->fields('s', array(
      'name',
      'schema_version',
    ))
      ->condition('s.status', 1)
      ->execute()
      ->fetchAllKeyed(0, 1);
  }
  $status = array(
    'maintenance_mode' => variable_get('maintenance_mode', FALSE),
    'projects_enabled' => $projects,
    'drupal_root' => DRUPAL_ROOT,
  );
  drupal_alter('drd_server_status', $status);
  return drd_server_result('drd.status', $status);
}

/**
 * Callback to retrieve lots of current runtime data form the domain.
 *
 * An extra parameter is provided: Period of time in seconds for which the data
 * should be received. If zero, all data will be returned.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_heartbeat() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  $period = array_shift($args);

  // Session count is "borrowed" from the admin_menu module, credit to their maintainers.
  $interval = REQUEST_TIME - variable_get('user_block_seconds_online', 900);
  $heartbeat = array(
    'count' => array(
      'user' => array(
        'all' => _drd_server_count('users', 'uid'),
        'sessions' => _drd_server_count_session($interval, TRUE),
        'auth' => _drd_server_count_session($interval, FALSE),
      ),
      'content' => array(
        'node' => _drd_server_count('node', 'nid'),
        'comment' => _drd_server_count('comment', 'cid'),
      ),
    ),
    'file' => array(
      'temp' => _drd_server_count_file(0),
      'perm' => _drd_server_count_file(1),
    ),
    'watchdog' => array(
      'emergency' => _drd_server_count_watchdog(WATCHDOG_EMERGENCY, $period),
      'alert' => _drd_server_count_watchdog(WATCHDOG_ALERT, $period),
      'critical' => _drd_server_count_watchdog(WATCHDOG_CRITICAL, $period),
      'error' => _drd_server_count_watchdog(WATCHDOG_ERROR, $period),
      'warning' => _drd_server_count_watchdog(WATCHDOG_WARNING, $period),
      'notice' => _drd_server_count_watchdog(WATCHDOG_NOTICE, $period),
      'info' => _drd_server_count_watchdog(WATCHDOG_INFO, $period),
      'debug' => _drd_server_count_watchdog(WATCHDOG_DEBUG, $period),
    ),
  );
  $heartbeat['detail'] = array(
    'user' => array(
      'title' => t('User'),
      'content' => array(
        format_plural($heartbeat['count']['user']['auth'], '@count authenticated user', '@count authenticated users'),
        format_plural($heartbeat['count']['user']['sessions'], '@count user session', '@count user sessions'),
        format_plural($heartbeat['count']['user']['all'], '@count registered user', '@count registered users'),
      ),
    ),
    'content' => array(
      'title' => t('Content'),
      'content' => array(
        format_plural($heartbeat['count']['content']['node'], '@count node', '@count nodes'),
        format_plural($heartbeat['count']['content']['comment'], '@count comment', '@count comments'),
      ),
    ),
    'file' => array(
      'title' => t('Files'),
      'content' => array(
        format_plural($heartbeat['file']['temp']['count'], '@count temporary file with %size', '@count temporary files with %size', array(
          '%size' => format_size($heartbeat['file']['temp']['size']),
        )),
        format_plural($heartbeat['file']['perm']['count'], '@count permanent file with %size', '@count permanent files with %size', array(
          '%size' => format_size($heartbeat['file']['perm']['size']),
        )),
      ),
    ),
    'watchdog' => array(
      'title' => t('Watchdog'),
      'content' => array(),
    ),
  );
  foreach ($heartbeat['watchdog'] as $key => $value) {
    $heartbeat['detail']['watchdog']['content'][$key] = format_plural($value, '@count %severity entry', '@count %severity entries', array(
      '%severity' => $key,
    ));
  }
  drupal_alter('drd_server_heartbeat', $heartbeat);
  return drd_server_result('drd.heartbeat', $heartbeat);
}

/**
 * @return string
 */
function drd_server_render_blocks() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  if (!module_exists('block')) {
    return drd_server_result('drd.render.blocks', '');
  }
  $result = '';
  foreach (variable_get('drd_server_blocks', array()) as $def) {
    if ($def) {
      list($module, $delta) = explode(':', $def);
      $block = block_load($module, $delta);
      $block_content = _block_render_blocks(array(
        $block,
      ));
      $build = _block_get_renderable_array($block_content);
      $result .= drupal_render($build);
    }
  }
  return drd_server_result('drd.render.blocks', $result);
}

/**
 * Callback to retrieve a (sub-)form for Core configuration.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_config_server_read() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  $form = module_invoke_all('drd_config_server');
  return drd_server_result('drd.config.read.server', $form);
}

/**
 * Callback to save Core configuration.
 *
 * Extra parameter: The values to be saved.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_config_server_save() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  $values = drupal_json_decode(array_shift($args));
  $form = module_invoke_all('drd_config_server');
  return drd_server_config_save($form, $values);
}

/**
 * Callback to retrieve a (sub-)form for Domain configuration.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_config_domain_read() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  $form = module_invoke_all('drd_config_domain');
  return drd_server_result('drd.config.read.domain', $form);
}

/**
 * Callback to save Domain configuration.
 *
 * Extra parameter: The values to be saved.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_config_domain_save() {
  $args = func_get_args();
  $valid = _drd_server_validate_request($args);
  if ($valid !== TRUE) {
    return $valid;
  }
  $values = drupal_json_decode(array_shift($args));
  $form = module_invoke_all('drd_config_domain');
  return drd_server_config_save($form, $values);
}

/**
 * Callback to save form values, either for Core or Domain.
 *
 * @param array $form
 *   Form definition for which the values should be stored.
 * @param array $values
 *   The values to be saved.
 * @param bool $return
 *   Whether to return the result string to be sent back to DRD.
 *
 * @return string
 *   The result string to be sent back to DRD.
 */
function drd_server_config_save($form, $values, $return = TRUE) {
  foreach ($form as $key => $element) {
    if (is_array($element) && substr($key, 0, 1) != '#') {
      drd_server_config_save($element, $values, FALSE);
    }
    if (!empty($values[$key])) {
      variable_set($key, $values[$key]);
    }
  }
  if ($return) {
    return drd_server_result('drd.config.save', TRUE);
  }
  return '';
}

/** ======================================================================
 *
 * Helper functions
 */

/**
 * Safely read the settings.php file and return the relevant variables.
 *
 * @param $shortname
 * @param $file
 * @return array
 */
function _drd_server_read_settings($shortname, $file) {
  $databases = array();
  try {
    $php = php_strip_whitespace($file);
    $php = str_replace('<?php', '', $php);
    $php = str_replace('<?', '', $php);
    $php = str_replace('?>', '', $php);
    $php = str_replace('ini_set', '@ini_set', $php);
    $php = str_replace('@@ini_set', '@ini_set', $php);
    eval($php);
  } catch (Exception $e) {

    // Ignore it
    _drd_server_watchdog('Read Settings - Exception occured:<pre>@exception</pre>', array(
      '@exception' => print_r($e, TRUE),
    ), WATCHDOG_ERROR);
    return array(
      '',
      '',
    );
  }
  if (empty($base_url)) {
    if ($shortname == 'default') {
      $base_url = $GLOBALS['base_url'];
    }
    else {
      $base_url = $shortname;
    }
  }
  return array(
    $base_url,
    $databases,
  );
}

/**
 * This is called to update encryption keys on all domains.
 *
 * @param array $domainurls
 *   An array of domain names as string for which the AES keys should be set.
 * @param array $aes_keys
 *   The array of AES keys for the given domains.
 */
function drd_server_key_remote($domainurls, $aes_keys) {
  $done = array();
  foreach ($domainurls as $url => $shortname) {
    if (in_array($shortname, $done)) {
      continue;
    }
    $done[] = $shortname;
    $databases = NULL;
    $file = DRUPAL_ROOT . '/sites/' . $shortname . '/settings.php';
    if (file_exists($file)) {
      list($base_url, $databases) = _drd_server_read_settings($shortname, $file);
      if (!empty($databases['default']['default'])) {
        try {
          $shortname = 'drd' . $shortname;
          Database::addConnectionInfo($shortname, $shortname, $databases['default']['default']);
          $db = Database::getConnection($shortname, $shortname);
          $db
            ->merge('variable')
            ->key(array(
            'name' => 'drd_aes_keys',
          ))
            ->fields(array(
            'value' => serialize($aes_keys),
          ))
            ->execute();
          $db
            ->query("DELETE FROM {cache_bootstrap} WHERE cid='variables'");
          Database::closeConnection($shortname, $shortname);
        } catch (Exception $ex) {
          watchdog_exception('DRD Server', $ex, 'Tried updating key for %shortname', array(
            '%shortname' => $shortname,
          ));
        }
      }
    }
  }
}

/**
 * Determines all available sites/domains in the current Drupal installation.
 *
 * @return array
 *   An array with key/value pairs where key is the domain name and value the shortname of a directory in
 *   DRUPAL_ROOT/sites/ for where to find the settings.php file for that domain.
 */
function drd_server_read_sites() {
  $sites = array();
  if (file_exists(DRUPAL_ROOT . '/sites/sites.php')) {
    try {
      include DRUPAL_ROOT . '/sites/sites.php';
    } catch (Exception $e) {

      // Ignore.
    }
  }
  if (empty($sites)) {
    foreach (scandir(DRUPAL_ROOT . '/sites') as $shortname) {
      if (is_dir(DRUPAL_ROOT . '/sites/' . $shortname) && !in_array($shortname, array(
        '.',
        '..',
        'all',
      ))) {
        $sites[$shortname] = $shortname;
      }
    }
  }
  if (!empty($sites)) {
    foreach ($sites as $key => $shortname) {
      if (is_dir(DRUPAL_ROOT . '/sites/' . $shortname)) {
        $file = DRUPAL_ROOT . '/sites/' . $shortname . '/settings.php';
        if (file_exists($file)) {
          list($base_url, $databases) = _drd_server_read_settings($shortname, $file);
          if (empty($base_url) && $key == $shortname) {
            _drd_server_watchdog('Reading Sites - Failed as url is empty: @shortname', array(
              '@shortname' => $shortname,
            ), WATCHDOG_ERROR);
            continue;
          }
          $pos = strpos($base_url, '://');
          if ($pos > 0) {
            $base_url = substr($base_url, $pos + 3);
          }

          // If we have a more accurate base_url go by that
          if ($base_url != $key && $base_url != $shortname) {
            $sites[$base_url] = $shortname;
            unset($sites[$key]);
          }
        }
      }
    }
  }
  _drd_server_watchdog('Reading Sites - Found @n entries: <pre>@list</pre>', array(
    '@n' => count($sites),
    '@list' => print_r($sites, TRUE),
  ));
  return $sites;
}

/**
 * @return array
 */
function drd_get_allowed_referers($refs = NULL) {
  if (!isset($refs)) {
    $refs = variable_get('drd_allowed_referer', '');
  }
  $refs = str_replace(',', ' ', $refs);
  $refs = str_replace(';', ' ', $refs);
  return explode(' ', $refs);
}

/**
 * Get the AES encryption values for the remote DRD dashboard, identified by their IP address.
 *
 * @return array
 *   An array of the AES encryption values for the given referer.
 */
function drd_server_aes() {
  $cluster_mode = !empty($_SERVER['HTTP_X_DRD_AUTH_TOKEN']);
  $referer = $cluster_mode ? $_SERVER['HTTP_X_DRD_AUTH_TOKEN'] : ip_address();
  $aes_keys = variable_get('drd_aes_keys', array());
  if (empty($aes_keys[$referer])) {
    return FALSE;
  }
  if (!empty($aes_keys[$referer]['cluster_mode']) && (empty($aes_keys[$referer]['cluster_ips']) || strpos($aes_keys[$referer]['cluster_ips'], ip_address()) === FALSE)) {
    return FALSE;
  }
  return $aes_keys[$referer];
}

/**
 * Prepare the result for returning it to the XMLRPC caller.
 *
 * @param string $mode
 *   The function that was called by the XMLRPC caller.
 * @param string $drd_result
 *   The result from the drd_server functions to be returned after hooks and alterers have been called.
 *
 * @return string
 *   Returns the final result to be sent to the XMLRPC caller.
 */
function drd_server_result($mode, $drd_result) {
  $args = func_get_args();
  $mode = array_shift($args);
  $drd_result = array_shift($args);
  foreach (module_implements('drd_server_result') as $module) {
    $drd_result = module_invoke($module, 'drd_server_result', $mode, $drd_result, $args);
  }
  drupal_alter('drd_server_result', $drd_result);
  $result = new stdClass();
  $result->message = $drd_result;
  $result->messages = drupal_get_messages();
  return drd_server_output($result);
}

/**
 * Prepare an error message for returning to the XMLRPC caller.
 *
 * @param mixed $message
 *   The message to be sent to the XMLRPC caller.
 * @param int $code
 *   An optional error code which determines whether the result can be encrypted before it's returned to DRD.
 *
 * @return string
 *   Returns the encoded message.
 */
function drd_server_error($message, $code = 1) {
  if (is_object($message)) {
    $message = (array) $message;
  }
  if (is_array($message)) {
    $message = implode(' ', $message);
  }
  $encrypt = !in_array($code, array(
    DRD_SERVER_ERROR_WRONG_REFERER,
    DRD_SERVER_ERROR_NO_REFERER,
    DRD_SERVER_ERROR_MISSING_AES,
    DRD_SERVER_ERROR_WRONG_KEYS,
  ));
  return drd_server_output(xmlrpc_error($code, strip_tags($message)), $encrypt);
}

/**
 * Prepare the output to be sent back to the DRD dashboard. This preparation
 * json encodes the output and then encrypts the resulting string if encryption
 * is available.
 *
 * @param mixed $output
 *   The output that should be returned to DRD and that needs to be prepared here.
 * @param boolean $encrypt
 *   TRUE, if the result should be encrypted.
 *
 * @return string
 *   The prepared string ready to be sent back to DRD.
 */
function drd_server_output($output, $encrypt = TRUE) {
  if (!empty($output)) {
    $output = drupal_json_encode($output);
    if ($encrypt) {
      $aes = drd_server_aes();
      if (!empty($aes)) {
        $output = drd_server_aes_encrypt($output, TRUE, $aes['key'], $aes['cipher'], _drd_server_iv(), $aes['impl']);
      }
    }
  }
  return $output;
}

/**
 * Callback for the url 'admin/drd_server/%' to find out if a proper result is
 * returned which indicates whether drd_server is installed on a certain domain
 * or not.
 *
 * @param int $var
 *   A values to be used checking if this domain is installed and reachable.
 */
function drd_server_check($var) {
  drupal_add_http_header('charset', 'utf-8');
  drupal_add_http_header('content-type', 'text/plain');
  drupal_send_headers();
  print _drd_server_get_check_token($GLOBALS['base_url'], $var);
  exit;
}

/**
 * @param string $url
 * @param int $var
 * @return string
 */
function _drd_server_get_check_token($url, $var) {
  return md5(implode('-', array(
    $var,
    $url,
    $var / 2,
  )));
}

/**
 * @param bool $debug
 * @return bool
 */
function _drd_server_debug_mode($debug = NULL) {
  static $debug_mode = FALSE;
  if (isset($debug)) {
    $debug_mode = $debug;
  }
  return $debug_mode;
}

/**
 * Internal watchdog replacement which only reports to watchdog if debugging
 * is switched on by the remote DRD dashboard.
 *
 * @param string $type
 *   Type of watchdog report.
 * @param string $message
 *   Message of the watchdog report.
 * @param array $variables
 *   Parameters for the watchdog report.
 * @param int $severity
 *   Severity of the watchdog report.
 * @param string $link
 *   Optional link associated with the watchdog report.
 */
function _drd_server_watchdog($message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
  if (_drd_server_debug_mode()) {
    watchdog('DRD Server', $message, $variables, $severity, $link);
  }
}

/**
 * Callback to get and decode the IV from the request header and store it for
 * the response.
 *
 * @return string
 */
function _drd_server_iv() {
  static $iv;
  if (!isset($iv)) {
    if (empty($_SERVER['HTTP_X_DRD_IV'])) {
      $iv = empty($_SERVER['HTTP_X_DRD_IV']);
    }
    else {
      $iv = base64_decode($_SERVER['HTTP_X_DRD_IV']);
    }
  }
  return $iv;
}

/**
 * Callback to validate a request that's coming from DRD.
 *
 * @param array $args
 *   An array of all arguments being submitted by DRD.
 * @param string $action_name
 *   If the callback from DRD is about to execute an action, this parameter
 *   determines the action name.
 *
 * @return bool|string
 *   All unencrypted arguments are in $args and the return value is either TRUE
 *   or contains an error message.
 */
function _drd_server_validate_request(&$args, $action_name = '') {
  $langcode = $args[2];
  $debug = $args[3];
  global $language;
  $language->language = $langcode;
  _drd_server_debug_mode($debug);
  $aes = drd_server_aes();
  if (empty($aes)) {
    _drd_server_watchdog('Execution request unauthorized.', array(), WATCHDOG_ALERT);
    return drd_server_error(t('Referer (%ip) not allowed.', array(
      '%ip' => ip_address(),
    )), DRD_SERVER_ERROR_WRONG_REFERER);
  }
  array_push($args, $action_name);
  $iv = _drd_server_iv();
  foreach ($args as $i => $value) {
    if (!empty($value) && !in_array($i, array(
      2,
      3,
    ))) {
      $args[$i] = drd_server_aes_decrypt($value, TRUE, $aes['key'], $aes['cipher'], $iv, $aes['impl']);
    }
  }
  $api = array_shift($args);
  $timestamp = array_shift($args);
  $langcode = array_shift($args);
  $debug = array_shift($args);
  if (!_drd_server_validate_timestamp($timestamp)) {
    _drd_server_watchdog('Wrong encryption keys.', array(), WATCHDOG_EMERGENCY);
    return drd_server_error(t('Wrong encryption keys.'), DRD_SERVER_ERROR_WRONG_KEYS);
  }
  if ($api !== DRD_SERVER_API_VERSION) {
    _drd_server_watchdog('Wrong API: %api.', array(
      '%api' => $api,
    ), WATCHDOG_ALERT);
    return drd_server_error(t('Wrong API.'), DRD_SERVER_ERROR_WRONG_API);
  }
  $action_name = empty($args[0]) ? t('simple') : $args[0];
  if (sizeof($args) < 2) {
    _drd_server_watchdog('Remote execution request: !action_name', array(
      '!action_name' => $action_name,
    ));
  }
  else {
    _drd_server_watchdog('Remote execution request: !action_name <pre>!args</pre>', array(
      '!action_name' => $action_name,
      '!args' => print_r($args, TRUE),
    ));
  }
  return TRUE;
}

/**
 * Callback to validate if $timestamp contains numbers only.
 *
 * @param int $timestamp
 *   The timestamp to be checked.
 *
 * @return boolean
 *   TRUE if $timestamp only contains digits, FALSE otherwise.
 */
function _drd_server_validate_timestamp($timestamp) {
  for ($i = 0; $i < strlen($timestamp); $i++) {
    if (strpos('0123456789', $timestamp[$i]) === FALSE) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Callback to count the number of records in a given database table.
 *
 * @param string $table
 *   The name of the database table from which the number of records should be counted.
 * @param string $primary
 *   The name of the primary keys in that database table.
 *
 * @return int
 *   The number of records being available.
 */
function _drd_server_count($table, $primary) {
  if (db_table_exists($table)) {
    $query = db_select($table);
    $query
      ->addExpression('COUNT(' . $primary . ')', 'count');
    return $query
      ->execute()
      ->fetchField();
  }
  return 0;
}

/**
 * Callback to count number of sessions in a given period for either anonymous or authenticated users.
 *
 * @param int $timestamp
 *   A Unix timestamp for the starting time from which the number of sessions should be counted until now.
 * @param boolean $anonymous
 *   If TRUE, only sessions for anonymous users will be counted, otherwise only sessions for authenticated
 *   user sessions will be counted.
 *
 * @return int
 *   The number of sessions being found.
 */
function _drd_server_count_session($timestamp = 0, $anonymous = TRUE) {
  $query = db_select('sessions');
  $query
    ->addExpression('COUNT(sid)', 'count');
  $query
    ->condition('timestamp', $timestamp, '>=');
  $query
    ->condition('uid', 0, $anonymous ? '=' : '>');
  return $query
    ->execute()
    ->fetchField();
}

/**
 * Callback to determine the number of managed files and their size.
 *
 * @param boolean $status
 *   If TRUE, only permanent files get considered, otherwise only temporary files.
 *
 * @return array
 *   Associated array with a key 'count' containing the number of files and 'size' containing the size in bytes of
 *   all those files together.
 */
function _drd_server_count_file($status) {
  $count_query = db_select('file_managed');
  $count_query
    ->addExpression('COUNT(fid)', 'count');
  $count = $count_query
    ->condition('status', $status)
    ->execute()
    ->fetchField();
  $size_query = db_select('file_managed');
  $size_query
    ->addExpression('SUM(filesize)', 'size');
  $size = $size_query
    ->condition('status', $status)
    ->execute()
    ->fetchField();
  return array(
    'count' => $count,
    'size' => $size,
  );
}

/**
 * Callback to count the number of watchdog entries of a given severity in a given period of time.
 *
 * @param string $severity
 *   The severity of records to be counted.
 * @param int $period
 *   The period in seconds in the past relative to now for which the records should be counted.
 *
 * @return int
 *   The number of records being available.
 */
function _drd_server_count_watchdog($severity, $period) {
  if (!module_exists('dblog')) {
    return 0;
  }
  $query = db_select('watchdog');
  $query
    ->addExpression('COUNT(wid)', 'count');
  $query
    ->condition('severity', $severity);
  if ($period) {
    $query
      ->condition('timestamp', REQUEST_TIME - $period, '>=');
  }
  return $query
    ->execute()
    ->fetchField();
}

Functions

Namesort descending Description
drd_get_allowed_referers
drd_server_aes Get the AES encryption values for the remote DRD dashboard, identified by their IP address.
drd_server_block_info Implements hook_block_info().
drd_server_block_view Implements hook_block_view().
drd_server_check Callback for the url 'admin/drd_server/%' to find out if a proper result is returned which indicates whether drd_server is installed on a certain domain or not.
drd_server_config_domain_read Callback to retrieve a (sub-)form for Domain configuration.
drd_server_config_domain_save Callback to save Domain configuration.
drd_server_config_save Callback to save form values, either for Core or Domain.
drd_server_config_server_read Callback to retrieve a (sub-)form for Core configuration.
drd_server_config_server_save Callback to save Core configuration.
drd_server_drd_config_domain Implements hook_drd_config_domain().
drd_server_drd_server_actions Implements hook_drd_server_actions().
drd_server_error Prepare an error message for returning to the XMLRPC caller.
drd_server_execute This is called to execute one of the actions that are defined on one of the hook_drd_server_actions().
drd_server_file_download Implements hook_file_download().
drd_server_heartbeat Callback to retrieve lots of current runtime data form the domain.
drd_server_key This is called to update the excryption keys for this server and all it's domains hosted in the same Drupal installation.
drd_server_key_remote This is called to update encryption keys on all domains.
drd_server_menu Implements hook_menu().
drd_server_menu_site_status_alter Implements hook_menu_site_status_alter().
drd_server_navbar Implements hook_navbar().
drd_server_output Prepare the output to be sent back to the DRD dashboard. This preparation json encodes the output and then encrypts the resulting string if encryption is available.
drd_server_permission Implements hook_permission().
drd_server_read_sites Determines all available sites/domains in the current Drupal installation.
drd_server_render_blocks
drd_server_result Prepare the result for returning it to the XMLRPC caller.
drd_server_status Callback to retrieve current status of the domain.
drd_server_xmlrpc Implementation of hook_xmlrpc().
_drd_server_count Callback to count the number of records in a given database table.
_drd_server_count_file Callback to determine the number of managed files and their size.
_drd_server_count_session Callback to count number of sessions in a given period for either anonymous or authenticated users.
_drd_server_count_watchdog Callback to count the number of watchdog entries of a given severity in a given period of time.
_drd_server_debug_mode
_drd_server_get_check_token
_drd_server_iv Callback to get and decode the IV from the request header and store it for the response.
_drd_server_read_settings Safely read the settings.php file and return the relevant variables.
_drd_server_validate_request Callback to validate a request that's coming from DRD.
_drd_server_validate_timestamp Callback to validate if $timestamp contains numbers only.
_drd_server_watchdog Internal watchdog replacement which only reports to watchdog if debugging is switched on by the remote DRD dashboard.

Constants