You are here

acquia_spi.module in Acquia Connector 7

Send site profile information (SPI) and system data to Acquia Network.

File

acquia_spi/acquia_spi.module
View source
<?php

/**
 * @file
 *   Send site profile information (SPI) and system data to Acquia Network.
 */

/**
 * Implements of hook_help().
 */
function acquia_spi_help($path, $arg) {
  if ($path == 'admin/help#acquia_spi' && ($welcome_nid = variable_get('acquia_welcome', 0))) {

    // Only provide help text if the welcome message is avalailable.
    if ($nid = db_query('SELECT nid FROM {node} where nid = :nid', array(
      ':nid' => $welcome_nid,
    ))
      ->fetchField()) {
      return '<p>' . t('The <a href="@welcome_url">Acquia Drupal welcome page</a> provides information about how to quickly get your site up and running. Also there are instructions for setting the site theme as well as many other configuration tasks.', array(
        '@welcome_url' => url('node/' . $nid),
      )) . '<p>';
    }
  }
}

/**
 * Implements hook_cron().
 */
function acquia_spi_cron() {
  $last = variable_get('acquia_spi_cron_last', 0);

  // 8 hour interval for sending site profile.
  $interval = variable_get('acquia_spi_cron_interval', 8 * 60 * 60);
  $now = REQUEST_TIME;
  if ($now - $last > $interval) {
    acquia_spi_send_profile_info();
    variable_set('acquia_spi_cron_last', $now);
  }
}

/**
 * Implements hook_form_[form_id]_alter().
 */
function acquia_spi_form_acquia_agent_settings_form_alter(&$form) {
  $form['cs']['buttons']['submit']['#submit'][] = 'acquia_spi_agent_settings_submit';
}

/**
 * Added submit function for acquia_agent_settings form.
 */
function acquia_spi_agent_settings_submit($form, &$form_state) {

  // Send information as soon as the key/identifier pair is submitted.
  acquia_spi_send_profile_info();
}

/**
 * Send site profile information to Acquia Network via XML-RPC.
 */
function acquia_spi_send_profile_info() {

  // Do nothing unless we have credentials.
  if (acquia_agent_has_credentials()) {
    $spi = acquia_spi_get();
    $data = acquia_agent_call('acquia.spi.update', $spi);
    return (bool) $data['result'];
  }
  return FALSE;
}

/**
 * Gather site profile information about this site.
 *
 * @return
 *   An associative array keyed by types of information.
 */
function acquia_spi_get() {

  // Include version number information.
  acquia_agent_load_versions();
  $acquia_version = $hashes = $fileinfo = array();
  $hashes_string = '';
  if (IS_ACQUIA_DRUPAL) {
    $acquia_version = array(
      'version' => ACQUIA_DRUPAL_VERSION,
      'series' => ACQUIA_DRUPAL_SERIES,
      'branch' => ACQUIA_DRUPAL_BRANCH,
      'revision' => ACQUIA_DRUPAL_REVISION,
    );

    // Get file hashes and compute serialized version.
    list($hashes, $fileinfo) = acquia_spi_file_hashes();
    $hashes_string = serialize($hashes);
  }
  return array(
    'modules' => acquia_spi_get_modules(),
    'platform' => acquia_spi_get_platform(),
    'quantum' => acquia_spi_get_quantum(),
    'file_hashes' => $hashes,
    'hashes_md5' => md5($hashes_string),
    'hashes_sha1' => sha1($hashes_string),
    'hashes_sha256' => hash('sha256', $hashes_string),
    'fileinfo' => $fileinfo,
    'acquia_version' => $acquia_version,
  );
}

/**
 * Gather platform specific information.
 *
 * @return
 *   An associative array keyed by a platform information type.
 */
function acquia_spi_get_platform() {

  // Webserver detection is based on name being before the slash, and
  // version being after the slash.
  preg_match('!^([^/]+)(/.+)?$!', $_SERVER['SERVER_SOFTWARE'], $webserver);
  $platform = array(
    'php' => PHP_VERSION,
    'webserver_type' => isset($webserver[1]) ? $webserver[1] : '',
    'webserver_version' => isset($webserver[2]) ? $webserver[2] : '',
    'database_type' => db_driver(),
    'database_version' => Database::getConnection()
      ->getAttribute(PDO::ATTR_SERVER_VERSION),
    'system_type' => php_uname('s'),
    // php_uname() only accepts one character, so we need to concatenate ourselves.
    'system_version' => php_uname('r') . ' ' . php_uname('v') . ' ' . php_uname('m') . ' ' . php_uname('n'),
  );

  // Never send NULL (or FALSE?) - that causes hmac errors.
  foreach ($platform as $key => $value) {
    if (empty($platform[$key])) {
      $platform[$key] = '';
    }
  }
  return $platform;
}

/**
 * Gather information about modules on the site.
 *
 * @return
 *   An associative array keyed by filename of associative arrays with
 *   information on the modules.
 */
function acquia_spi_get_modules() {
  $modules = system_rebuild_module_data();
  $result = array();
  $keys_to_send = array(
    'name',
    'version',
    'package',
    'core',
    'project',
  );
  foreach ($modules as $filename => $file) {
    if (!empty($file->info['hidden'])) {

      // Skip hidden modules.
      continue;
    }
    $info = array();
    $info['status'] = $file->status;
    foreach ($keys_to_send as $key) {
      $info[$key] = isset($file->info[$key]) ? $file->info[$key] : '';
    }
    $info['filename'] = $file->filename;
    $result[] = $info;
  }
  return $result;
}

/**
 * Gather information about nodes, users and comments.
 *
 * @return
 *   An associative array.
 */
function acquia_spi_get_quantum() {
  $quantum = array();

  // Get only published nodes.
  $quantum['nodes'] = db_query("SELECT COUNT(nid) FROM {node} WHERE status = 1")
    ->fetchField();

  // Get only active users.
  $quantum['users'] = db_query("SELECT COUNT(uid) FROM {users} WHERE status = 1")
    ->fetchField();

  // Get only active comments. NOTE: in case of comments, 0 is the active comment!
  if (module_exists('comment')) {
    $quantum['comments'] = db_query("SELECT COUNT(cid) FROM {comment} WHERE status = :comment_constant", array(
      ':comment_constant' => COMMENT_PUBLISHED,
    ))
      ->fetchField();
  }
  else {
    $quantum['comments'] = 0;
  }
  return $quantum;
}

/**
 * Gather hashes of all important files, ignoring line ending and CVS Ids
 *
 * @param $excuded_dirs
 *   Optional array of directory paths to be excluded.
 *
 * @return
 *   An associative array keyed by filename of hashes.
 */
function acquia_spi_file_hashes($exclude_dirs = array()) {

  // The list of directories for the third parameter are the only ones that
  // will be recursed into.  Thus, we avoid sending hashes for any others.
  list($hashes, $fileinfo) = _acquia_spi_generate_hashes('.', $exclude_dirs, array(
    'modules',
    'profiles',
    'themes',
    'includes',
    'misc',
    'scripts',
  ));
  ksort($hashes);
  return array(
    $hashes,
    $fileinfo,
  );
}

/**
 * Recursive helper function for acquia_spi_file_hashes().
 */
function _acquia_spi_generate_hashes($dir, $exclude_dirs, $limit_dirs = array()) {
  $hashes = array();
  $fileinfo = array();
  if (is_dir($dir) && ($handle = opendir($dir))) {
    while ($file = readdir($handle)) {
      if (!in_array($file, array(
        '.',
        '..',
        'CVS',
        '.svn',
        '.git',
        '.bzr',
      ))) {
        $path = $dir == '.' ? $file : "{$dir}/{$file}";
        if (is_dir($path) && !in_array($path, $exclude_dirs) && (empty($limit_dirs) || in_array($path, $limit_dirs)) && $file != 'translations') {
          list($sub_hashes, $sub_fileinfo) = _acquia_spi_generate_hashes($path, $exclude_dirs);
          $hashes = array_merge($sub_hashes, $hashes);
          $fileinfo = array_merge($sub_fileinfo, $fileinfo);
          $hashes[$path] = acquia_spi_hash_path($path);
        }
        elseif (acquia_spi_is_manifest_type($file)) {
          $hashes[$path] = acquia_spi_hash_path($path);
          $owner = fileowner($path);
          if (function_exists('posix_getpwuid')) {
            $userinfo = posix_getpwuid($owner);
            $owner = $userinfo['name'];
          }
          $fileinfo[$path] = 'mt:' . filemtime($path) . '$p:' . substr(sprintf('%o', fileperms($path)), -4) . '$o:' . $owner . '$s:' . filesize($path);
        }
      }
    }
    closedir($handle);
  }
  return array(
    $hashes,
    $fileinfo,
  );
}

/**
 * Determine if a path is a file type we care about for modificaitons.
 */
function acquia_spi_is_manifest_type($path) {
  $extensions = array(
    'php' => 1,
    'php4' => 1,
    'php5' => 1,
    'module' => 1,
    'inc' => 1,
    'install' => 1,
    'test' => 1,
    'theme' => 1,
    'engine' => 1,
    'profile' => 1,
    'css' => 1,
    'js' => 1,
    'info' => 1,
    'sh' => 1,
    // SSL certificates
    'pem' => 1,
    'pl' => 1,
    'pm' => 1,
  );
  $pathinfo = pathinfo($path);
  return isset($pathinfo['extension']) && isset($extensions[$pathinfo['extension']]);
}

/**
 * Calculate the sha1 hash for a path.
 *
 * @param $path
 *   The name of the file or a directory.
 * @return
 *   bas64 encoded sha256 hash. 'hash' is an empty string for directories.
 */
function acquia_spi_hash_path($path = '') {
  $hash = '';
  if (file_exists($path)) {
    if (!is_dir($path)) {
      $string = file_get_contents($path);

      // Remove trailing whitespace
      $string = rtrim($string);

      // Replace all line endings and CVS/svn Id tags
      $string = preg_replace('/\\$Id[^;<>{}\\(\\)\\$]*\\$/', 'x$' . 'Id$', $string);
      $string = preg_replace('/\\r\\n|\\n|\\r/', ' ', $string);
      $hash = base64_encode(hash('sha256', $string, TRUE));
    }
  }
  return $hash;
}

Functions

Namesort descending Description
acquia_spi_agent_settings_submit Added submit function for acquia_agent_settings form.
acquia_spi_cron Implements hook_cron().
acquia_spi_file_hashes Gather hashes of all important files, ignoring line ending and CVS Ids
acquia_spi_form_acquia_agent_settings_form_alter Implements hook_form_[form_id]_alter().
acquia_spi_get Gather site profile information about this site.
acquia_spi_get_modules Gather information about modules on the site.
acquia_spi_get_platform Gather platform specific information.
acquia_spi_get_quantum Gather information about nodes, users and comments.
acquia_spi_hash_path Calculate the sha1 hash for a path.
acquia_spi_help Implements of hook_help().
acquia_spi_is_manifest_type Determine if a path is a file type we care about for modificaitons.
acquia_spi_send_profile_info Send site profile information to Acquia Network via XML-RPC.
_acquia_spi_generate_hashes Recursive helper function for acquia_spi_file_hashes().