commit 24f82d70d88502f80158447694595c48027506d0
Author: pwolanin <pwolanin@49851.no-reply.drupal.org>
Date:   Wed Mar 16 10:19:21 2011 -0400

    Issue #1094634 add acquia_search module for 7.x

diff --git a/README.txt b/README.txt
index 0ebeb76..ff9178c 100644
--- a/README.txt
+++ b/README.txt
@@ -28,6 +28,9 @@ Acquia Site profile: Automates the collection of site information -
 operating system, database, webserver, and PHP versions, installed modules,
 and site modifications - to speed support communication and issue resolution.
 
+Acquia Search: Provides integration between your Drupal site and Acquia's
+hosted search service. Requires Apache Solr Search Integration module.
+
 Installation
 --------------------------------------------------------------------------------
 
diff --git a/acquia_search/Acquia_Search_Service.php b/acquia_search/Acquia_Search_Service.php
new file mode 100644
index 0000000..9baec9a
--- /dev/null
+++ b/acquia_search/Acquia_Search_Service.php
@@ -0,0 +1,217 @@
+<?php
+// $Id: Acquia_Search_Service.php 5183 2010-01-08 04:28:12Z buildbot $
+
+/**
+ * Starting point for the Solr API. Represents a Solr server resource and has
+ * methods for pinging, adding, deleting, committing, optimizing and searching.
+ *
+ */
+class AcquiaSearchService extends DrupalApacheSolrService {
+
+  protected function add_request_id(&$url) {
+    $id = uniqid();
+    if (!stristr($url,'?')) {
+      $url .= "?";
+    }
+    else {
+      $url .= "&";
+    }
+    $url .= 'request_id=' . $id;
+    return $id;
+  }
+  /**
+   * Central method for making a get operation against this Solr Server
+   *
+   * @see Drupal_Apache_Solr_Service::_sendRawGet()
+   */
+  protected function _sendRawGet($url, $timeout = FALSE) {
+    $id = $this->add_request_id($url);
+    list($cookie, $nonce) = acquia_search_auth_cookie($url);
+    if (empty($cookie)) {
+      throw new Exception('Invalid authentication string - subscription keys expired or missing.');
+    }
+    $request_headers = array(
+      'Cookie' => $cookie,
+      'User-Agent' => 'acquia_search/'. variable_get('acquia_search_version', '7.x'),
+    );
+    list ($data, $headers) = $this->_makeHttpRequest($url, 'GET', $request_headers, '', $timeout);
+    $response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
+    $hmac = acquia_search_extract_hmac($headers);
+    $code = (int) $response->getHttpStatus();
+    if ($code != 200) {
+      $message = $response->getHttpStatusMessage() . "\n request ID: $id \n";
+      if ($code >= 400 && $code != 403 && $code != 404) {
+        // Add details, like Solr's exception message.
+        $message .= $response->getRawResponse();
+      }
+      throw new Exception('"' . $code . '" Status: ' . $message);
+    }
+    elseif (!acquia_search_valid_response($hmac, $nonce, $data)) {
+      throw new Exception('Authentication of search content failed url: '. $url);
+    }
+    return $response;
+  }
+
+  /**
+   * Central method for making a post operation against this Solr Server
+   *
+   * @see Drupal_Apache_Solr_Service::_sendRawGet()
+   */
+  protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8')  {
+    if (variable_get('apachesolr_read_only', 0)) {
+      throw new Exception('Operating in read-only mode; updates are disabled.');
+    }
+    $id = $this->add_request_id($url);
+    list($cookie, $nonce) = acquia_search_auth_cookie($url, $rawPost);
+    if (empty($cookie)) {
+      throw new Exception('Invalid authentication string - subscription keys expired or missing.');
+    }
+    $request_headers = array(
+      'Content-Type' => $contentType,
+      'Cookie' => $cookie,
+      'User-Agent' => 'acquia_search/'. variable_get('acquia_search_version', '7.x'),
+    );
+    list ($data, $headers) = $this->_makeHttpRequest($url, 'POST', $request_headers, $rawPost, $timeout);
+    $response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
+    $code = (int) $response->getHttpStatus();
+    if ($code != 200) {
+      $message = $response->getHttpStatusMessage() . "\n request ID: $id \n";
+      if ($code >= 400 && $code != 403 && $code != 404) {
+        // Add details, like Solr's exception message.
+        $message .= $response->getRawResponse();
+      }
+      throw new Exception('"' . $code . '" Status: ' . $message);
+    }
+    return $response;
+  }
+
+  /**
+   * Make a request to a servlet (a path) that's not a standard path.
+   *
+   * @param string $servlet
+   *   A path to be added to the base Solr path. e.g. 'extract/tika'
+   *
+   * @param array $params
+   *   Any request parameters when constructing the URL.
+   *
+   * @param string $method
+   *   'GET', 'POST', 'PUT', or 'HEAD'.
+   *
+   * @param array $request_headers
+   *   Keyed array of header names and values.  Should include 'Content-Type'
+   *   for POST or PUT.
+   *
+   * @param string $rawPost
+   *   Must be an empty string unless method is POST or PUT.
+   *
+   * @param float $timeout
+   *   Read timeout in seconds or FALSE.
+   *
+   * @return 
+   *  Apache_Solr_Response object
+   */
+  public function makeServletRequest($servlet, $params = array(), $method = 'GET', $request_headers = array(), $rawPost = '', $timeout = FALSE) {
+    if ($method == 'GET' || $method == 'HEAD') {
+      // Make sure we are not sending a request body.
+      $rawPost = '';
+    }
+    // Add default params.
+    $params += array(
+      'wt' => self::SOLR_WRITER,
+    );
+
+    $url = $this->_constructUrl($servlet, $params);
+    $id = $this->add_request_id($url);
+    // We assume we only authenticate the URL for other servlets.
+    list($cookie, $nonce) = acquia_search_auth_cookie($url);
+    if (empty($cookie)) {
+      throw new Exception('Invalid authentication string - subscription keys expired or missing.');
+    }
+    $request_headers += array(
+      'Cookie' => $cookie,
+      'User-Agent' => 'acquia_search/'. variable_get('acquia_search_version', '7.x'),
+    );
+    list ($data, $headers) = $this->_makeHttpRequest($url, $method, $request_headers, $rawPost, $timeout);
+    $response = new Apache_Solr_Response($data, $headers, $this->_createDocuments, $this->_collapseSingleValueArrays);
+    $hmac = acquia_search_extract_hmac($headers);
+    $code = (int) $response->getHttpStatus();
+    if ($code != 200) {
+      $message = $response->getHttpStatusMessage();
+      if ($code >= 400 && $code != 403 && $code != 404) {
+        // Add details, like Solr's exception message.
+        $message .= $response->getRawResponse();
+      }
+      throw new Exception('"' . $code . '" Status: ' . $message);
+    }
+    elseif (!acquia_search_valid_response($hmac, $nonce, $data)) {
+      throw new Exception('Authentication of search content failed url: '. $url);
+    }
+    return $response;
+  }
+
+  /**
+   * Send an optimize command.
+   *
+   * We want to control the schedule of optimize commands ourselves,
+   * so do a method override to make ->optimize() a no-op.
+   *
+   * @see Drupal_Apache_Solr_Service::optimize()
+   */
+  public function optimize($waitFlush = true, $waitSearcher = true, $timeout = 3600) {
+    return TRUE;
+  }
+
+  protected function _makeHttpRequest($url, $method = 'GET', $headers = array(), $content = '', $timeout = FALSE) {
+    // Set a response timeout
+    if ($timeout) {
+      $default_socket_timeout = ini_set('default_socket_timeout', $timeout);
+    }
+
+    $ctx = acquia_agent_stream_context_create($url, 'acquia_search');
+    if (!$ctx) {
+      throw new Exception(t("Could not create stream context"));
+    }
+
+    $result = drupal_http_request($url, array(
+      'headers' => $headers, 
+      'method' => $method, 
+      'data' => $content, 
+      'context' => $ctx
+    ));
+
+    // Restore the response timeout
+    if ($timeout) {
+      ini_set('default_socket_timeout', $default_socket_timeout);
+    }
+
+    if (!isset($result->code) || $result->code < 0) {
+      $result->code = '0';
+      $result->status_message = 'Request failed';
+    }
+
+    if (!isset($result->status_message)) {
+      $result->status_message = '';
+    }
+
+    if (isset($result->error)) {
+      $responses[0] .= ': ' . check_plain($result->error);
+    }
+
+    if (!isset($result->data)) {
+      $result->data = '';
+    }
+
+    if (!isset($result->error)) {
+      $result->error = '';
+    }
+
+    $headers[] = "{$result->protocol} {$result->code} {$result->status_message}. {$result->error}";
+    if (isset($result->headers)) {
+      foreach ($result->headers as $name => $value) {
+        $headers[] = "$name: $value";
+      }
+    }
+    return array($result->data, $headers);
+  }
+
+}
diff --git a/acquia_search/acquia_search.info b/acquia_search/acquia_search.info
new file mode 100644
index 0000000..7bbbfa3
--- /dev/null
+++ b/acquia_search/acquia_search.info
@@ -0,0 +1,17 @@
+name = Acquia search
+description = "Provides integration between your Drupal site and Acquia's hosted search service."
+package = Acquia Network Connector
+core = 7.x
+project = acquia_search
+project status url = http://updates.acquia.com/release-history
+
+dependencies[] = acquia_agent
+dependencies[] = apachesolr
+dependencies[] = apachesolr_search
+dependencies[] = search
+
+files[] = acquia_search.install
+files[] = acquia_search.module
+files[] = Acquia_Search_Service.php
+
+
diff --git a/acquia_search/acquia_search.install b/acquia_search/acquia_search.install
new file mode 100644
index 0000000..af126c8
--- /dev/null
+++ b/acquia_search/acquia_search.install
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * Implementation of hook_requirements().
+ */
+function acquia_search_requirements($phase) {
+  $requirements = array();
+  // Ensure translations don't break at install time
+  $t = get_t();
+
+  // Skip install checks if install.php is running. The weak install profile
+  // API means install.php calls hook_requirements for every module in a profile.
+  if ($phase == 'install' && defined('MAINTENANCE_MODE') && MAINTENANCE_MODE != 'install') {
+    if (module_invoke('acquia_agent', 'has_credentials')) {
+      $severity = REQUIREMENT_OK;
+    }
+    else {
+      $severity = REQUIREMENT_ERROR;
+    }
+    $requirements['acquia_search_credentials'] = array(
+      'description' => $t('In order to use Acquia search module you must have an Acquia Network subscription. Please enter your Acquia Network subscription keys.'),
+      'severity' => $severity,
+      'value' => '',
+    );
+
+  }
+  // Check SSL support.
+  if (in_array('ssl', stream_get_transports(), TRUE)) {
+    $severity = REQUIREMENT_OK;
+    $requirements['acquia_search'] = array(
+      'description' => $t('The Acquia Search module is using SSL to protect the privacy of your content.'),
+    );
+  }
+  else {
+    $severity = REQUIREMENT_WARNING;
+    $requirements['acquia_search'] = array(
+      'description' => $t('In order to protect the privacy of your content with the Acquia Search module you must have SSL support enabled in PHP on your host.'),
+    );
+  }
+  $requirements['acquia_search']['title'] = $t('Acquia Search');
+  $requirements['acquia_search']['severity'] = $severity;
+  $requirements['acquia_search']['value'] = '';
+
+  return $requirements;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function acquia_search_install() {
+
+  $facets = _acquia_search_get_default_facets();
+  
+  apachesolr_save_enabled_facets(ACQUIA_SEARCH_SERVER_ID, $facets);
+
+  $theme_key = variable_get('theme_default', 'bartik');
+  $block_regions = array_keys(system_region_list($theme_key));
+  $region = _acquia_search_find_block_region($block_regions);
+
+  if (empty($region)) {
+    drupal_set_message(t('We were unable to auto-detect where to put the filter blocks for your faceted search engine. To get started adding blocks, go to the <a href="@active_filter_config_form">filter configuration screen</a>.', array('@active_filter_config_form' => url('admin/config/apachesolr/active_filters'))));
+    return;
+  }
+
+  foreach ($facets['apachesolr_search'] as $delta => $facet_field) {
+    $blocks[] = array (
+      'module' => 'apachesolr_search',
+      'delta' => $delta,
+    );
+  }
+
+  $blocks[] = array (
+    'module' => 'apachesolr',
+    'delta' => 'sort',
+    'weight' => -1
+  );
+
+  foreach ($blocks as $block) {
+    $block['cache'] = DRUPAL_CACHE_PER_PAGE;
+    $block['theme'] = $theme_key;
+    $block['region'] = $region;
+    $block['status'] = 1;
+    // {block}.pages is type 'text', so it cannot have a
+    // default value, and is not null, so we must provide one.
+    $block['pages']  = '';
+    // Make sure the block does not already exist.
+    if (!db_query("SELECT 1 FROM {block} WHERE theme = ':theme' AND module = ':module' AND delta = ':delta'", 
+      array(':theme' => $block['theme'], ':module' => $block['module'], ':delta' => $block['delta']))->fetchField()){
+      drupal_write_record('blocks', $block);
+    }
+  }
+}
+
+/**
+ * Helper function for hook_install().
+ */
+function _acquia_search_find_block_region($block_regions = array()) {
+  $regions_to_look_for = array (
+     'left', /* garland & zen */
+     'sidebar_first', /* acquia */
+  );
+
+  if ($matches = array_intersect($block_regions, $regions_to_look_for)) {
+    return array_shift($matches);
+  }
+}
+
+/**
+ * Helper function for hook_install().
+ */
+function _acquia_search_get_default_facets() {
+
+  $default_enabled_facets['apachesolr_search']['bundle'] = 'bundle';
+  $default_enabled_facets['apachesolr_search']['is_uid'] =  'is_uid';
+
+  return $default_enabled_facets;
+}
+
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function acquia_search_uninstall() {
+  // Nothing to do, but allows the module to be uninstalled.
+}
+
diff --git a/acquia_search/acquia_search.module b/acquia_search/acquia_search.module
new file mode 100644
index 0000000..e5cfe88
--- /dev/null
+++ b/acquia_search/acquia_search.module
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * @file
+ *   Integration between Acquia Drupal and Acquia's hosted solr search service.
+ */
+
+define('ACQUIA_SEARCH_SERVER_ID', 'acquia_search_server_1');
+
+/**
+ * Predefined Acquia Search network server
+ */
+function acquia_search_get_server() {
+  $acquia_server = array(
+    'server_id' => ACQUIA_SEARCH_SERVER_ID,
+    'name' => t('Acquia Search'),
+    'host' => variable_get('acquia_search_host', 'search.acquia.com'),
+    'port' => variable_get('acquia_search_port', '80'),
+    'path' => variable_get('acquia_search_path', '/solr/'. acquia_agent_settings('acquia_identifier')),
+    'service_class' => 'AcquiaSearchService');
+  return $acquia_server;
+}
+
+/**
+ * Implementation of hook_enable().
+ */
+function acquia_search_enable() {
+  if (acquia_agent_subscription_is_active()) {
+    acquia_search_lock_acquia_solr_server();
+    // Send a heartbeat so the Acquia Network knows the module is enabled.
+    acquia_agent_check_subscription();
+  }
+}
+
+/**
+ * Implementation of hook_init().
+ */
+function acquia_search_init() {
+  if (arg(0) == 'admin' && arg(1) == 'config' && arg(2) == 'search' && arg(3) == 'apachesolr'
+      && user_access('administer search')
+      && acquia_agent_subscription_is_active()
+      && variable_get('apachesolr_default_server') == ACQUIA_SEARCH_SERVER_ID
+      && empty($_POST)) {
+    $as_link = l(t('Acquia Search'), 'http://acquia.com/products-services/acquia-search');
+    drupal_set_message(t("Search is being provided by the !as network service.", array('!as' => $as_link)));
+  }
+}
+
+/**
+ * Create a new record pointing to the Acquia apachesolr search server and set it as the default
+ */
+function acquia_search_lock_acquia_solr_server() {
+  apachesolr_server_save(acquia_search_get_server());
+
+  variable_set('apachesolr_default_server', ACQUIA_SEARCH_SERVER_ID);
+  if (!variable_get('apachesolr_failure', FALSE)) {
+    variable_set('apachesolr_failure', 'show_drupal_results');
+  }
+}
+
+/**
+ * Implementation of hook_disable().
+ */
+function acquia_search_disable() {
+  acquia_search_unlock_acquia_solr_server();
+}
+
+/**
+ * Helper function to clear variables we may have set.
+ */
+function acquia_search_unlock_acquia_solr_server() {
+  apachesolr_server_delete(ACQUIA_SEARCH_SERVER_ID);
+  $servers = apachesolr_load_all_servers();
+  if (empty($servers)) {
+    apachesolr_server_save(array('server_id' => 'solr', 'name' => 'Apache Solr server', 'host' => 'localhost', 'port' => '8983', 'path' => '/solr'));
+    variable_set('apachesolr_default_server', 'solr');
+  }
+  else {
+    variable_set('apachesolr_default_server', key($servers));
+  }
+}
+
+/**
+ * Implementation of hook_menu_alter().
+ */
+function acquia_search_menu_alter(&$menu) {
+  $delete_page = 'admin/config/search/apachesolr/server/%apachesolr_server/delete';
+  if (isset($menu[$delete_page])) {
+    $menu[$delete_page]['access callback'] = 'acquia_search_server_delete_access';
+    $menu[$delete_page]['access arguments'] = array(5);
+  };
+}
+
+function acquia_search_cron() {
+  // Cache the cersion in a variable so we can send it at not extra cost.
+  $version = variable_get('acquia_search_version', '7.x');
+  $info = system_get_info('module', 'acquia_search');
+  // Send the version, or at least the core compatibility as a fallback.
+  $new_version = isset($info['version']) ? (string)$info['version'] : (string)$info['core'];
+  if ($version != $new_version) {
+    variable_set('acquia_search_version', $new_version);
+  }
+}
+
+/**
+ * Delete server page access.
+ */
+function acquia_search_server_delete_access($server) {
+  if ($server['server_id'] == ACQUIA_SEARCH_SERVER_ID) {
+    return FALSE;
+  }
+  // Fall back to the original check.
+  return user_access('administer search');  
+}
+
+/**
+ * Implementation of hook_form_[form_id]_alter().
+ */
+function acquia_search_form_apachesolr_settings_alter(&$form, $form_state) {
+  // Don't alter the form if there is no subscription.
+  if (acquia_agent_subscription_is_active()) {
+    // Don't show delete operation for the AS server
+    foreach ($form['apachesolr_host_settings']['table']['#rows'] as &$row) {
+      if (isset($row['data'][2]['data']) && strpos($row['data'][2]['data'], 'apachesolr/server/' . ACQUIA_SEARCH_SERVER_ID . '/delete') !== FALSE) {
+        $row['data'][2]['data'] = '';
+        break;
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_form_[form_id]_alter().
+ */
+function acquia_search_form_apachesolr_server_edit_form_alter(&$form, $form_state) {
+  // Do not allow editing of Acquia Search server parameters
+  if (isset($form_state['build_info']['args'][0]['server_id']) && $form_state['build_info']['args'][0]['server_id'] == ACQUIA_SEARCH_SERVER_ID ) {
+    $form['host']['#disabled'] = TRUE; 
+    $form['port']['#disabled'] = TRUE;
+    $form['path']['#disabled'] = TRUE;
+    $form['name']['#disabled'] = TRUE;
+    $form['server_id']['#disabled'] = TRUE;    
+  }
+
+  $form['save']['#validate'][] = 'acquia_search_server_edit_form_validate';
+}
+
+function acquia_search_server_edit_form_validate($form, &$form_state) {
+  if ($form_state['values']['server_id'] == ACQUIA_SEARCH_SERVER_ID) {
+    // make sure that the server parameters has not been changed
+    $form_state['values'] = array_merge($form_state['values'], acquia_search_get_server());
+  }
+}
+
+/**
+ * Implementation of hook_acquia_subscription_status().
+ */
+
+function acquia_search_acquia_subscription_status($active) {
+  if ($active) {
+    acquia_search_lock_acquia_solr_server();
+  }
+  else {
+    acquia_search_unlock_acquia_solr_server();
+  }
+}
+
+/**
+ * Modify a solr base url and construct a hmac authenticator cookie.
+ *
+ * @param $url
+ *  The solr url beng requested - passed by reference and may be altered.
+ * @param $string
+ *  A string - the data to be authenticated, or empty to just use the path
+ *  and query from the url to build the authenticator.
+ * @param $derived_key
+ *  Optional string to supply the derived key.
+ *
+ * @return
+ *  An array containing the string to be added as the content of the
+ *  Cookie header to the request and the nonce.
+ */
+function acquia_search_auth_cookie(&$url, $string = '', $derived_key = NULL) {
+  $uri = parse_url($url);
+
+  // Add a scheme - should always be https if available.
+  if (in_array('ssl', stream_get_transports(), TRUE) && !defined('ACQUIA_DEVELOPMENT_NOSSL')) {
+    $scheme = 'https://';
+    $port = '';
+  }
+  else {
+    $scheme = 'http://';
+    $port = (isset($uri['port']) && $uri['port'] != 80) ? ':'. $uri['port'] : '';
+  }
+  $path = isset($uri['path']) ? $uri['path'] : '/';
+  $query = isset($uri['query']) ? '?'. $uri['query'] : '';
+  $url = $scheme . $uri['host'] . $port . $path . $query;
+
+  // 32 character nonce.
+  $nonce = base64_encode(drupal_random_bytes(24));
+
+  if ($string) {
+    $auth_header = acquia_search_authenticator($string, $nonce, $derived_key);
+  }
+  else {
+    $auth_header = acquia_search_authenticator($path . $query, $nonce, $derived_key);
+  }
+  return array($auth_header, $nonce);
+}
+
+/**
+ * Derive a key for the solr hmac using the information shared with acquia.com.
+ */
+function _acquia_search_derived_key() {
+  static $derived_key;
+  if (!isset($derived_key)) {
+    $key = acquia_agent_settings('acquia_key');
+    $subscription = acquia_agent_settings('acquia_subscription_data');
+    $identifier = acquia_agent_settings('acquia_identifier');
+    // We use a salt from acquia.com in key derivation since this is a shared
+    // value that we could change on the AN side if needed to force any
+    // or all clients to use a new derived key.  We also use a string
+    // ('solr') specific to the service, since we want each service using a
+    // derived key to have a separate one.
+    if (empty($subscription['active']) || empty($key) || empty($identifier)) {
+      // Expired or invalid subscription - don't continue.
+      $derived_key = '';
+    }
+    else {
+      $salt = isset($subscription['derived_key_salt']) ? $subscription['derived_key_salt'] : '';
+      $derivation_string = $identifier . 'solr' . $salt;
+      $derived_key =  hash_hmac('sha1', str_pad($derivation_string, 80, $derivation_string), $key);
+    }
+  }
+  return $derived_key;
+}
+
+/**
+ * Creates an authenticator based on a data string and HMAC-SHA1.
+ */
+function acquia_search_authenticator($string, $nonce, $derived_key = NULL) {
+  if (empty($derived_key)) {
+    $derived_key = _acquia_search_derived_key();
+  }
+  if (empty($derived_key)) {
+    // Expired or invalid subscription - don't continue.
+    return '';
+  }
+  else {
+    $time = REQUEST_TIME;
+    return 'acquia_solr_time='. $time .'; acquia_solr_nonce='. $nonce .'; acquia_solr_hmac='. hash_hmac('sha1', $time . $nonce . $string, $derived_key) .';';
+  }
+}
+
+/**
+ * Validate the authenticity of returned data using a nonce and HMAC-SHA1.
+ *
+ * @return
+ *  TRUE or FALSE.
+ */
+function acquia_search_valid_response($hmac, $nonce, $string, $derived_key = NULL) {
+  if (empty($derived_key)) {
+    $derived_key = _acquia_search_derived_key();
+  }
+  return $hmac == hash_hmac('sha1', $nonce . $string, $derived_key);
+}
+
+/**
+ * Look in the headers and get the hmac_digest out
+ * @return string hmac_digest
+ *
+ */
+function acquia_search_extract_hmac($http_response_header) {
+  $reg = array();
+  if (is_array($http_response_header)) {
+    foreach ($http_response_header as $header) {
+      if (preg_match("/Pragma:.*hmac_digest=(.+);/i", $header, $reg)) {
+        return trim($reg[1]);
+      }
+    }
+  }
+  return '';
+}
+
+
diff --git a/acquia_search/search.acquia.com.pem b/acquia_search/search.acquia.com.pem
new file mode 100644
index 0000000..84f7597
--- /dev/null
+++ b/acquia_search/search.acquia.com.pem
@@ -0,0 +1,185 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 927650371 (0x374ad243)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Validity
+            Not Before: May 25 16:09:40 1999 GMT
+            Not After : May 25 16:39:40 2019 GMT
+        Subject: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:cd:28:83:34:54:1b:89:f3:0f:af:37:91:31:ff:
+                    af:31:60:c9:a8:e8:b2:10:68:ed:9f:e7:93:36:f1:
+                    0a:64:bb:47:f5:04:17:3f:23:47:4d:c5:27:19:81:
+                    26:0c:54:72:0d:88:2d:d9:1f:9a:12:9f:bc:b3:71:
+                    d3:80:19:3f:47:66:7b:8c:35:28:d2:b9:0a:df:24:
+                    da:9c:d6:50:79:81:7a:5a:d3:37:f7:c2:4a:d8:29:
+                    92:26:64:d1:e4:98:6c:3a:00:8a:f5:34:9b:65:f8:
+                    ed:e3:10:ff:fd:b8:49:58:dc:a0:de:82:39:6b:81:
+                    b1:16:19:61:b9:54:b6:e6:43
+                Exponent: 3 (0x3)
+        X509v3 extensions:
+            Netscape Cert Type: 
+                SSL CA, S/MIME CA, Object Signing CA
+            X509v3 CRL Distribution Points: 
+                DirName:/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority/CN=CRL1
+                URI:http://www.entrust.net/CRL/net1.crl
+
+            X509v3 Private Key Usage Period: 
+                Not Before: May 25 16:09:40 1999 GMT, Not After: May 25 16:09:40 2019 GMT
+            X509v3 Key Usage: 
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+            X509v3 Subject Key Identifier: 
+                F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+            X509v3 Basic Constraints: 
+                CA:TRUE
+            1.2.840.113533.7.65.0: 
+                0
+..V4.0....
+    Signature Algorithm: sha1WithRSAEncryption
+        90:dc:30:02:fa:64:74:c2:a7:0a:a5:7c:21:8d:34:17:a8:fb:
+        47:0e:ff:25:7c:8d:13:0a:fb:e4:98:b5:ef:8c:f8:c5:10:0d:
+        f7:92:be:f1:c3:d5:d5:95:6a:04:bb:2c:ce:26:36:65:c8:31:
+        c6:e7:ee:3f:e3:57:75:84:7a:11:ef:46:4f:18:f4:d3:98:bb:
+        a8:87:32:ba:72:f6:3c:e2:3d:9f:d7:1d:d9:c3:60:43:8c:58:
+        0e:22:96:2f:62:a3:2c:1f:ba:ad:05:ef:ab:32:78:87:a0:54:
+        73:19:b5:5c:05:f9:52:3e:6d:2d:45:0b:f7:0a:93:ea:ed:06:
+        f9:b2
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1116122016 (0x4286aba0)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Validity
+            Not Before: Jul 14 17:10:28 2006 GMT
+            Not After : Jul 14 17:40:28 2014 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:c4:3c:bc:cc:ba:ea:62:e6:9e:42:23:af:b4:b2:
+                    11:ae:62:8d:d9:a1:d3:8f:cc:14:72:19:ed:99:f6:
+                    fd:de:5d:bb:59:c0:b0:c2:af:6a:99:52:95:57:0b:
+                    35:ff:e1:86:fa:af:a2:c3:c1:05:65:27:20:76:2f:
+                    0a:54:25:7c:ea:2b:dd:95:41:57:9b:0f:3b:cf:47:
+                    e8:01:06:8c:c5:44:48:fc:07:2e:f3:72:83:30:51:
+                    94:9e:69:3a:83:a9:87:90:14:d9:5e:c4:a2:78:8f:
+                    c1:3e:d4:19:61:0e:1a:e7:dc:1a:95:20:41:be:6f:
+                    17:da:b8:1c:41:ee:98:b0:fa:d7:1d:35:3f:16:a7:
+                    04:28:13:5d:3d:05:75:9a:2e:86:9e:2c:b0:17:87:
+                    20:58:57:ef:e1:ea:55:60:b8:2c:70:21:ac:29:63:
+                    0c:10:fc:60:bc:f9:d5:1c:37:64:d5:b4:fb:1e:ab:
+                    26:39:46:88:a4:cb:cd:30:67:c0:26:79:e5:ca:68:
+                    f9:22:d7:e5:d2:b1:bb:48:4c:9b:0b:98:5b:42:92:
+                    e5:d2:ed:0e:47:30:fa:dd:27:56:63:6a:a5:01:c7:
+                    8e:af:f0:4e:3b:1b:55:15:44:d5:3e:4d:57:1e:e1:
+                    91:ea:b8:a0:8f:ce:3a:63:5f:96:89:ba:08:3e:fe:
+                    44:bb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.2.840.113533.7.75.2
+                  CPS: http://www.entrust.net/cps
+                  User Notice:
+                    Explicit Text: For use solely with SSL and S/MIME certificates issued by Digicert, Inc. to authorized subscribers.
+DOES NOT represent any endorsement by Entrust Inc. or its affiliates as to the identity of any certificate holder.
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, E-mail Protection, OCSP Signing
+            X509v3 CRL Distribution Points: 
+                URI:http://crl.entrust.net/server1.crl
+                DirName:/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority/CN=CRL1
+
+            X509v3 Key Usage: 
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+            X509v3 Subject Key Identifier: 
+                A7:C7:13:A0:7A:01:3C:9D:EF:82:48:82:48:D5:73:51:B6:12:56:2A
+            1.2.840.113533.7.65.0: 
+                0
+..V7.1....
+    Signature Algorithm: sha1WithRSAEncryption
+        4a:f1:b3:ce:68:69:e3:58:a3:61:ed:b6:16:c8:93:b1:18:30:
+        3e:e0:72:df:4f:3d:e2:4d:e1:b8:fc:3f:c1:c9:e3:45:a9:5d:
+        a9:c1:da:a3:e5:36:d7:8e:d6:0f:ad:36:af:6c:bc:44:e6:9a:
+        46:94:a6:11:a0:d0:52:e7:da:55:b0:f6:1c:85:fb:63:52:7b:
+        b6:99:8f:1f:e2:cb:47:64:a2:eb:08:65:e6:51:db:1c:4b:6d:
+        7f:53:9f:1e:0d:31:93:d4:30:5d:1e:6e:01:07:27:60:5d:bb:
+        d3:f4:e3:6c:1d:20:0f:75:2a:33:10:a7:b7:89:d7:a9:17:14:
+        32:03
+-----BEGIN CERTIFICATE-----
+MIIGJDCCBY2gAwIBAgIEQoaroDANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjA3
+MTQxNzEwMjhaFw0xNDA3MTQxNzQwMjhaMFwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
+EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xGzAZBgNV
+BAMTEkRpZ2lDZXJ0IEdsb2JhbCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMQ8vMy66mLmnkIjr7SyEa5ijdmh04/MFHIZ7Zn2/d5du1nAsMKvaplS
+lVcLNf/hhvqvosPBBWUnIHYvClQlfOor3ZVBV5sPO89H6AEGjMVESPwHLvNygzBR
+lJ5pOoOph5AU2V7EoniPwT7UGWEOGufcGpUgQb5vF9q4HEHumLD61x01PxanBCgT
+XT0FdZouhp4ssBeHIFhX7+HqVWC4LHAhrCljDBD8YLz51Rw3ZNW0+x6rJjlGiKTL
+zTBnwCZ55cpo+SLX5dKxu0hMmwuYW0KS5dLtDkcw+t0nVmNqpQHHjq/wTjsbVRVE
+1T5NVx7hkeq4oI/OOmNflom6CD7+RLsCAwEAAaOCAwUwggMBMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwggEyBgNVHSAEggEpMIIBJTCCASEGCSqGSIb2fQdLAjCCARIwJgYI
+KwYBBQUHAgEWGmh0dHA6Ly93d3cuZW50cnVzdC5uZXQvY3BzMIHnBggrBgEFBQcC
+AjCB2hqB10ZvciB1c2Ugc29sZWx5IHdpdGggU1NMIGFuZCBTL01JTUUgY2VydGlm
+aWNhdGVzIGlzc3VlZCBieSBEaWdpY2VydCwgSW5jLiB0byBhdXRob3JpemVkIHN1
+YnNjcmliZXJzLg0KRE9FUyBOT1QgcmVwcmVzZW50IGFueSBlbmRvcnNlbWVudCBi
+eSBFbnRydXN0IEluYy4gb3IgaXRzIGFmZmlsaWF0ZXMgYXMgdG8gdGhlIGlkZW50
+aXR5IG9mIGFueSBjZXJ0aWZpY2F0ZSBob2xkZXIuMDEGA1UdJQQqMCgGCCsGAQUF
+BwMBBggrBgEFBQcDAgYIKwYBBQUHAwQGCCsGAQUFBwMJMIIBGAYDVR0fBIIBDzCC
+AQswKKAmoCSGImh0dHA6Ly9jcmwuZW50cnVzdC5uZXQvc2VydmVyMS5jcmwwgd6g
+gduggdikgdUwgdIxCzAJBgNVBAYTAlVTMRQwEgYDVQQKEwtFbnRydXN0Lm5ldDE7
+MDkGA1UECxMyd3d3LmVudHJ1c3QubmV0L0NQUyBpbmNvcnAuIGJ5IHJlZi4gKGxp
+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0
+ZWQxOjA4BgNVBAMTMUVudHJ1c3QubmV0IFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEwCwYDVR0PBAQDAgEGMB8GA1Ud
+IwQYMBaAFPAXYhNVPbP/CgBr+1CEl/PtYtAaMB0GA1UdDgQWBBSnxxOgegE8ne+C
+SIJI1XNRthJWKjAZBgkqhkiG9n0HQQAEDDAKGwRWNy4xAwIAgTANBgkqhkiG9w0B
+AQUFAAOBgQBK8bPOaGnjWKNh7bYWyJOxGDA+4HLfTz3iTeG4/D/ByeNFqV2pwdqj
+5TbXjtYPrTavbLxE5ppGlKYRoNBS59pVsPYchftjUnu2mY8f4stHZKLrCGXmUdsc
+S21/U58eDTGT1DBdHm4BBydgXbvT9ONsHSAPdSozEKe3idepFxQyAw==
+-----END CERTIFICATE-----
diff --git a/acquia_search/tests/acquia_search.test b/acquia_search/tests/acquia_search.test
new file mode 100644
index 0000000..d7223f5
--- /dev/null
+++ b/acquia_search/tests/acquia_search.test
@@ -0,0 +1,65 @@
+<?php
+
+// For local testing, don't try to use SSL.
+if (!defined('ACQUIA_DEVELOPMENT_NOSSL')) {
+  define('ACQUIA_DEVELOPMENT_NOSSL', 1);
+}
+
+class AcquiaSearchTest extends DrupalTestCase {
+  function get_info() {
+    return array(
+      'name' => t('Acquia Search'),
+      'desc' => t('Acquia Search unit and functional tests.'),
+      'group' => t('Acquia'),
+    );
+  }
+  function setUp() {
+  	parent::setUp('acquia_agent', 'search', 'apachesolr', 'apachesolr_search', 'acquia_search');
+  }
+
+  function testHMAC() {
+    $this->DrupalVariableSet('acquia_identifier', 'valid_identifier');
+    $this->DrupalVariableSet('acquia_key', 'valid_key');
+    $this->DrupalVariableSet('acquia_subscription_data', array('active' => TRUE));
+
+    $key = $this->randomName();
+    $time = REQUEST_TIME;
+    $nonce = md5($this->randomName());
+    $string = $time . $nonce . $this->randomName();
+    $hash1 = hash_hmac('sha1', $string, $key); 
+    $hash2 = _acquia_search_hmac($key, $string);
+    // The results of these two hmac function must be equal if
+    // ours is correct.  We don't use the PHP built-in since for
+    // the module since it may be missing depending on compile
+    // options.
+    $this->assertEqual($hash1, $hash2, 'Same result using hash_hmac() and _acquia_search_hmac().');
+    $derived_key = _acquia_search_derived_key();
+    // The response is cehcked using the derived key, so construct another hmac.
+    $hash3 = hash_hmac('sha1', $nonce . $string, $derived_key);
+    $this->assertTrue(acquia_search_valid_response($hash3, $nonce, $string), 'Response HMAC would be accepted as valid.');
+  }
+  
+  function testDefaultBlocksEnable() {
+    
+    require_once drupal_get_path('module','acquia_search') . '/acquia_search.install';
+    $themes_regions = array (
+      'garland' => 'left',
+      'acquia_marina' => 'sidebar_first',
+      'madeuptheme' => null
+    );
+    foreach ($themes_regions as $theme => $expected_region) {
+      
+      if ($theme == 'madeuptheme') {
+        $block_regions = array('something', 'or', 'theother');
+      } else {
+        $block_regions = array_keys(system_region_list($theme));
+      }
+      $region = _acquia_search_find_block_region($block_regions);
+      $this->assertEqual($region, $expected_region);
+    }
+    
+    $facets = _acquia_search_get_default_facets();
+    $this->assertNotEqual($facets,array());
+  }
+}
+
