You are here

acquia_search.module in Acquia Connector 7.2

Integration between Acquia Drupal and Acquia's hosted solr search service.

File

acquia_search/acquia_search.module
View source
<?php

/**
 * @file
 *   Integration between Acquia Drupal and Acquia's hosted solr search service.
 */
use Drupal\acquia_search\PreferredSearchCoreService;
define('ACQUIA_SEARCH_ENVIRONMENT_ID', 'acquia_search_server_1');
define('ACQUIA_SEARCH_OVERRIDE_AUTO_SET', 1);
define('ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY', 2);
define('ACQUIA_SEARCH_EXISTING_OVERRIDE', 3);

/**
 * Implements hook_init().
 */
function acquia_search_init() {

  // Add apachesolr module connection overrides if applicable.
  $auto_switch_disabled = variable_get('acquia_search_disable_auto_switch', 0);
  $sub_active = acquia_agent_subscription_is_active();
  if (!$auto_switch_disabled && $sub_active && !module_exists('acquia_search_multi_subs')) {
    module_load_include('php', 'acquia_search', 'src/PreferredSearchCoreService');
    global $conf;
    $acquia_identifier = acquia_agent_settings('acquia_identifier');
    $ah_env = isset($_SERVER['AH_SITE_ENVIRONMENT']) ? $_SERVER['AH_SITE_ENVIRONMENT'] : '';
    $sites_foldername = substr(conf_path(), strrpos(conf_path(), '/') + 1);
    $ah_db_name = isset($conf['acquia_hosting_site_info']['db']['name']) ? $conf['acquia_hosting_site_info']['db']['name'] : '';
    $subscription = acquia_agent_settings('acquia_subscription_data');
    $available_cores = acquia_search_available_cores($subscription);
    $environments = apachesolr_load_all_environments();
    $core_service = new PreferredSearchCoreService($acquia_identifier, $ah_env, $sites_foldername, $ah_db_name, $available_cores);
    $overrode = acquia_search_add_apachesolr_overrides($core_service, $environments);
    if ($overrode) {

      // If an override was applied, then clear the corresponding cache item.
      cache_clear_all('apachesolr:environments', 'cache_apachesolr');
    }
  }
}

/**
 * Overrides apachesolr configs to talk to the proper Acquia search core.
 *
 * It determines proper (preferred) Acquia search core to which the site should
 * be connected and overrides the apachesolr config accordingly. If a proper
 * search core is not found, it enables the read-only mode so that the site
 * does not unintentionally connect to the wrong core and corrupts its index.
 *
 * @param Drupal\acquia_search\PreferredSearchCoreService $core_service
 * @param array $environments
 *   The apachesolr environments.
 *
 * @return bool|NULL
 *   True if preferred core has been found in the list of available cores and
 *   it was used to override the search settings.
 */
function acquia_search_add_apachesolr_overrides($core_service, $environments) {
  global $conf;
  $overrode = FALSE;
  foreach ($environments as $acquia_env_id => $environment) {

    // Override Acquia search environments only.
    if ($environment['service_class'] != 'AcquiaSearchService') {
      continue;
    }

    // Skip if the Acquia search environment has already been overridden.
    if (!empty($conf['apachesolr_environments'][$acquia_env_id])) {
      continue;
    }
    $conf['apachesolr_environments'][$acquia_env_id]['acquia_search_possible_cores'] = $core_service
      ->getListOfPossibleCores();
    if ($core_service
      ->isPreferredCoreAvailable()) {
      $derived_key = acquia_search_get_derived_key_for_core($core_service
        ->getPreferredCoreId());
      $conf['apachesolr_environments'][$acquia_env_id]['url'] = $core_service
        ->getPreferredCoreUrl();
      $conf['apachesolr_environments'][$acquia_env_id]['conf'] = array(
        'acquia_search_key' => $derived_key,
      );
      $conf['apachesolr_environments'][$acquia_env_id]['overridden_by_acquia_search'] = ACQUIA_SEARCH_OVERRIDE_AUTO_SET;
      drupal_static_reset('apachesolr_load_all_environments');
      $overrode = TRUE;
      continue;
    }

    // At this point none of the available search cores match to what is
    // expected. Switch the search into read-only mode.
    if (!variable_get('acquia_search_disable_auto_read_only', 0)) {
      $conf['apachesolr_environments'][$acquia_env_id]['conf'] = array(
        'apachesolr_read_only' => 1,
      );
      $conf['apachesolr_environments'][$acquia_env_id]['overridden_by_acquia_search'] = ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY;
      drupal_static_reset('apachesolr_load_all_environments');
      $overrode = TRUE;
    }
  }
  return $overrode;
}

/**
 * Returns derived key for the given core ID.
 *
 * @param $core_id
 *
 * @return string
 */
function acquia_search_get_derived_key_for_core($core_id) {
  $salt = acquia_search_derived_key_salt();
  $key = acquia_agent_settings('acquia_key');
  return _acquia_search_create_derived_key($salt, $core_id, $key);
}

/**
 * Retrieves all available search cores as set in the subscription.
 *
 * @param $subscription
 *   Acquia Subscription array.
 *
 * @return array
 */
function acquia_search_available_cores($subscription) {
  if (!empty($subscription['heartbeat_data']['search_cores'])) {
    return $subscription['heartbeat_data']['search_cores'];
  }
}

/**
 * Pings the search core.
 *
 * @param string $env_id
 * @param string $op
 *
 * @return bool
 */
function acquia_search_ping($env_id, $op = 'ping') {
  try {
    return (bool) apachesolr_get_solr($env_id)
      ->{$op}();
  } catch (Exception $e) {
    watchdog_exception('acquia_search', $e, 'Exception thrown when calling @op on Apachesolr. %type: !message in %function (line %line of %file).', array(
      '@op' => $op,
    ));
  }
}

/**
 * Adds Acquia search connection details to the given form.
 *
 * @param array $form
 * @param array $form_state
 * @param array $environment
 *   An Apache Solr module environment.
 */
function acquia_search_add_form_status_message(&$form, &$form_state, $environment) {
  $form['acquia_search_message'] = array(
    '#type' => 'fieldset',
    '#title' => t('Acquia Search status for this connection'),
    '#collapsible' => FALSE,
    '#weight' => -10,
  );
  $form['acquia_search_message']['message'] = array(
    '#markup' => acquia_search_get_search_status_message($environment),
  );
}

/**
 * Returns formatted message about read-only mode.
 *
 * @param array $environment
 *   An Apache Solr module environment.
 * @param string $t
 *
 * @return string
 */
function acquia_search_get_read_only_mode_warning($environment, $t = 't') {
  $msg = $t('To protect your data, Acquia Search module is enforcing
    read-only mode, because it could not figure out what Acquia-hosted Solr
    index to connect to. This helps you avoid writing to a production index
    if you copy your site to a development or other environment(s).');
  if (!empty($environment['acquia_search_possible_cores'])) {
    $list = theme('item_list', array(
      'items' => $environment['acquia_search_possible_cores'],
    ));
    $msg .= '<p>';
    $msg .= $t('These index IDs would have worked, but could not be found on
      your Acquia subscription: !list', array(
      '!list' => $list,
    ));
    $msg .= '</p>';
  }
  $msg .= $t('To fix this problem, please read <a href="@url">our documentation</a>.', array(
    '@url' => 'https://docs.acquia.com/acquia-search/multiple-cores',
  ));
  return $msg;
}

/**
 * Returns formatted message about Acquia Search connection details.
 *
 * @param array $environment
 *   An Apache Solr module environment.
 * @return string
 */
function acquia_search_get_search_status_message($environment) {
  $items = array(
    t('apachesolr.module environment ID: @env', array(
      '@env' => $environment['env_id'],
    )),
    t('URL: @url', array(
      '@url' => $environment['url'],
    )),
  );
  if (acquia_search_ping($environment['env_id'])) {
    $items[] = t('Solr index is currently reachable and up.');
  }
  else {
    $items[] = t('Solr index is currently unreachable.');
  }

  // Deep-ping the Solr index to ensure authentication is working.
  if (acquia_search_ping($environment['env_id'], 'getFields')) {
    $items[] = t('Requests to Solr index are passing authentication checks.');
  }
  else {
    $items[] = t('Solr core authentication check fails.');
  }
  return t('Connection managed by Acquia Search module.') . theme('item_list', array(
    'items' => $items,
  ));
}

/**
 * Predefined Acquia Search network environment
 */
function acquia_search_get_environment($conf = array()) {
  if (!empty($conf['acquia_subscription_id']) && !empty($conf['acquia_subscription_key'])) {
    $identifier = $conf['acquia_subscription_id'];
    $key = $conf['acquia_subscription_key'];
    $subscription = acquia_agent_get_subscription($params = array(), $identifier, $key);
  }
  else {
    $identifier = acquia_agent_settings('acquia_identifier');
    $subscription = acquia_agent_settings('acquia_subscription_data');
  }
  $search_host = variable_get('acquia_search_host', 'search.acquia.com');

  // @todo rework this logic once we have AH_SERVER_REGION
  // legacy_internal will be true if 'internal-' is the start of the host
  // name that's set in via Acquia Cloud platform or vset.
  $legacy_internal = preg_match('/internal[-.]/', $search_host);

  // Adding the subscription specific colony to the heartbeat data
  if (!empty($subscription['heartbeat_data']['search_service_colony'])) {
    $search_host = $subscription['heartbeat_data']['search_service_colony'];
    if ($legacy_internal) {

      // If we want to be using an internal host name, fix the one
      // from the hearbeat data here.
      $search_host = 'internal-' . $search_host;
    }
  }

  // Check if we are on Acquia Cloud hosting. @see NN-2503
  if (!empty($_ENV['AH_SITE_ENVIRONMENT']) && !empty($_ENV['AH_SERVER_REGION'])) {
    if ($_ENV['AH_SERVER_REGION'] == 'us-east-1' && $search_host == 'search.acquia.com') {
      $search_host = 'internal-search.acquia.com';
    }
    elseif (strpos($search_host, 'search-' . $_ENV['AH_SERVER_REGION']) === 0) {
      $search_host = 'internal-' . $search_host;
    }
  }
  $environment = array(
    'url' => 'http://' . $search_host . variable_get('acquia_search_path', '/solr/' . $identifier),
    'service_class' => 'AcquiaSearchService',
  );
  return $environment;
}

/**
 * Implementation of hook_enable().
 */
function acquia_search_enable() {

  // Send a heartbeat so the Acquia Subscription knows the module is enabled.
  // This causes an invocation of hook_acquia_subscription_status() which is
  // implemented in this module to set up the environment.
  _acquia_search_set_version();
  acquia_agent_check_subscription();
}

/**
 * Implementation of hook_help().
 */
function acquia_search_help($path, $arg) {
  switch ($path) {
    case 'admin/config/search/apachesolr':
      $env_id = isset($arg[5]) ? $arg[5] : apachesolr_default_environment();
      $environment = apachesolr_environment_load($env_id);
      if (acquia_search_environment_connected($environment) && acquia_agent_subscription_is_active()) {
        $as_link = l(t('Acquia Search'), 'http://www.acquia.com/products-services/acquia-search');
        return t("Search is being provided by the !as network service.", array(
          '!as' => $as_link,
        ));
      }
      break;
  }
}

/**
 * Create a new record pointing to the Acquia apachesolr search server and set it as the default
 */
function acquia_search_enable_acquia_solr_environment() {

  // Creates the new environment
  $environment = apachesolr_environment_load(ACQUIA_SEARCH_ENVIRONMENT_ID);
  if (!$environment) {
    $environment['conf'] = array();

    // Copy the bundles from the previous default environment
    $orig_env_id = apachesolr_default_environment();
    $orig_env = apachesolr_environment_load($orig_env_id);
    $environment['index_bundles'] = $orig_env['index_bundles'];

    // Also make sure that the default search page has Acquia Search as its
    // default environment
    $default_search_page_id = apachesolr_search_default_search_page();
    $default_search_page = apachesolr_search_page_load($default_search_page_id);
    if (!empty($default_search_page) && $default_search_page['env_id'] != ACQUIA_SEARCH_ENVIRONMENT_ID) {
      $default_search_page['env_id'] = ACQUIA_SEARCH_ENVIRONMENT_ID;
      apachesolr_search_page_save($default_search_page);
    }

    // Only set the default if we just created the environment.
    // This will almost always happen, unless the module was disabled via SQL.
    variable_set('apachesolr_default_environment', ACQUIA_SEARCH_ENVIRONMENT_ID);

    // Make sure apachesolr search is the default search module.
    variable_set('search_default_module', 'apachesolr_search');
  }
  $acquia_environment = acquia_search_get_environment();

  // Override default values
  foreach ($acquia_environment as $key => $value) {
    $environment[$key] = $value;
  }
  $environment['env_id'] = ACQUIA_SEARCH_ENVIRONMENT_ID;
  $environment['name'] = t('Acquia Search');

  // allow other modules to override this
  drupal_alter('acquia_search_enable', $environment);
  apachesolr_environment_save($environment);
}

/**
 * Implementation of hook_disable().
 *
 * Helper function to clear variables we may have set.
 */
function acquia_search_disable() {

  // Revert the default search environment
  if (apachesolr_default_environment() == ACQUIA_SEARCH_ENVIRONMENT_ID) {
    acquia_search_disable_revert_defaults();
  }

  // Remove the base Acquia Search environment we added.
  apachesolr_environment_delete(ACQUIA_SEARCH_ENVIRONMENT_ID);

  // Unset all other acquia search environments
  $environments = apachesolr_load_all_environments();
  foreach ($environments as $environment) {
    if (acquia_search_environment_connected($environment)) {

      // remove traces of acquia_search
      // unset our acquia url and set it back to default
      $environment['url'] = 'http://localhost:8983/solr';

      // Emptying the service class, unsetting it would not work, since it would
      // not overwrite the old value
      $environment['service_class'] = '';
      apachesolr_environment_save($environment);
    }
  }
  variable_del('acquia_search_derived_key_salt');
}

/**
 * Helper function to revert the default environment after disable
 */
function acquia_search_disable_revert_defaults() {

  // if the environment does not exists, it will return FALSE
  // Clear all caches to be sure.
  cache_clear_all('apachesolr:environments', 'cache_apachesolr');
  drupal_static_reset('apachesolr_load_all_environments');
  drupal_static_reset('apachesolr_get_solr');
  if (module_exists('ctools')) {
    ctools_include('export');
    ctools_export_load_object_reset('apachesolr_environment');
  }
  if (!apachesolr_environment_load('solr')) {
    module_load_include('inc', 'apachesolr', 'apachesolr.index');

    // Same statements as during the apachesolr install process
    // @todo make this less duplicate
    db_insert('apachesolr_environment')
      ->fields(array(
      'env_id' => 'solr',
      'name' => 'localhost server',
      'url' => 'http://localhost:8983/solr',
    ))
      ->execute();

    // Initialize the entities to index. We enable all node types by default
    $info = entity_get_info('node');
    $bundles = array_keys($info['bundles']);
    apachesolr_index_set_bundles('solr', 'node', $bundles);
  }

  // Go back to the default variable which is 'solr'.
  variable_del('apachesolr_default_environment');
}

/**
 * Implementation of hook_menu_alter().
 */
function acquia_search_menu_alter(&$menu) {
  $delete_page = 'admin/config/search/apachesolr/settings/%apachesolr_environment/delete';
  if (isset($menu[$delete_page])) {
    $menu[$delete_page]['access callback'] = 'acquia_search_environment_delete_access';
    $menu[$delete_page]['access arguments'] = array(
      5,
    );
  }
}

/**
 * Helper function to cache the Acquia Search version.
 */
function _acquia_search_set_version() {

  // Cache the version 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);
  }
}

/**
 * Tests whether the environment is connected to Acquia Search.
 */
function acquia_search_environment_connected($environment) {
  if ($environment['service_class'] == 'AcquiaSearchService') {
    return TRUE;
  }
  $acquia_search_key = apachesolr_environment_variable_get($environment['env_id'], 'acquia_search_key');
  if (!empty($acquia_search_key)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Delete environment page access.
 */
function acquia_search_environment_delete_access($environment) {
  if ($environment['env_id'] == ACQUIA_SEARCH_ENVIRONMENT_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 Default AS environment. This means
    // that cloned acquia search environments can be deleted
    foreach ($form['apachesolr_host_settings']['table']['#rows'] as &$row) {
      if (isset($row['data']['delete']['data']) && strpos($row['data']['delete']['data'], ACQUIA_SEARCH_ENVIRONMENT_ID . '/delete') !== FALSE) {
        $row['data']['delete']['data'] = '';
        break;
      }
    }
    $form['advanced']['acquia_search_edismax_default'] = array(
      '#type' => 'radios',
      '#title' => t('Always allow advanced syntax for Acquia Search'),
      '#default_value' => variable_get('acquia_search_edismax_default', 0),
      '#options' => array(
        0 => t('Disabled'),
        1 => t('Enabled'),
      ),
      '#description' => t('If enabled, all Acquia Search keyword searches may use advanced <a href="@url">Lucene syntax</a> such as wildcard searches, fuzzy searches, proximity searches, boolean operators and more via the Extended Dismax parser. If not enabled, this syntax wll only be used when needed to enable wildcard searches.', array(
        '@url' => 'http://lucene.apache.org/java/2_9_3/queryparsersyntax.html',
      )),
      '#weight' => 10,
    );
  }
}

/**
 * Implementation of hook_form_[form_id]_alter().
 */
function acquia_search_form_apachesolr_environment_edit_form_alter(&$form, $form_state) {

  // Gets environment from form, gets connection status to Acquia Search.
  $env_id = isset($form['env_id']['#default_value']) ? $form['env_id']['#default_value'] : '';
  $environment = $env_id ? apachesolr_environment_load($env_id) : FALSE;

  // Add message to Acquia-connected indexes.
  if ($environment && acquia_search_environment_connected($environment)) {
    acquia_search_add_form_status_message($form, $form_state, $environment);
    if (acquia_search_is_environment_overridden_to_read_only($environment)) {
      $dsm = acquia_search_get_read_only_mode_warning($environment);
      drupal_set_message($dsm, 'warning', FALSE);
    }
    $form['conf']['apachesolr_read_only']['#disabled'] = TRUE;
    $form['url']['#disabled'] = TRUE;
    $form['url']['#type'] = 'hidden';
    $form['env_id']['#disabled'] = TRUE;
  }

  // Don't let the user delete the initial environment.
  if ($env_id == ACQUIA_SEARCH_ENVIRONMENT_ID) {
    $form['name']['#disabled'] = TRUE;
    $form['actions']['delete']['#access'] = FALSE;
  }
  $form['actions']['save']['#validate'][] = 'acquia_search_environment_edit_form_validate';
}

/**
 * Implementation of hook_form_[form_id]_alter().
 */
function acquia_search_form_apachesolr_index_action_form_alter(&$form, $form_state) {
  $env_id = isset($form['action']['env_id']['#value']) ? $form['action']['env_id']['#value'] : '';
  $environment = $env_id ? apachesolr_environment_load($env_id) : FALSE;

  // Add message to Acquia-connected indexes.
  if ($environment && acquia_search_environment_connected($environment)) {
    acquia_search_add_form_status_message($form, $form_state, $environment);
    if (acquia_search_is_environment_overridden_to_read_only($environment)) {
      $dsm = acquia_search_get_read_only_mode_warning($environment);
      drupal_set_message($dsm, 'warning', FALSE);
    }
  }
}

/**
 * Determines whether the given environment is in the read-only mode.
 *
 * @param $environment
 *   Apachesolr environment.
 *
 * @return bool
 *   True if the environment has been switched to read-only mode by Acquia
 *   Search.
 */
function acquia_search_is_environment_overridden_to_read_only($environment) {
  return isset($environment['overridden_by_acquia_search']) && $environment['overridden_by_acquia_search'] == ACQUIA_SEARCH_AUTO_OVERRIDE_READ_ONLY;
}
function acquia_search_environment_edit_form_validate($form, &$form_state) {
  if ($form_state['values']['env_id'] == ACQUIA_SEARCH_ENVIRONMENT_ID) {

    // make sure that the environment parameters have not been changed
    $form_state['values'] = array_merge($form_state['values'], acquia_search_get_environment());
  }
}

/**
 * Implementation of hook_acquia_subscription_status().
 */
function acquia_search_acquia_subscription_status($active, $subscription = FALSE) {
  if ($active) {
    acquia_search_enable_acquia_solr_environment();

    // Refresh the salt with the subscription data returned by the heartbeat
    // since it can change periodically.
    $salt = variable_get('acquia_search_derived_key_salt', '');
    if (isset($subscription['derived_key_salt']) && $salt != $subscription['derived_key_salt']) {
      variable_set('acquia_search_derived_key_salt', $subscription['derived_key_salt']);
    }
  }
  else {
    if (is_int($subscription)) {
      switch ($subscription) {
        case SUBSCRIPTION_NOT_FOUND:
        case SUBSCRIPTION_EXPIRED:
          acquia_search_disable();
          break;
      }
    }
  }
}

/**
 * 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, $env_id = 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, $env_id);
  }
  else {
    $auth_header = acquia_search_authenticator($path . $query, $nonce, $derived_key, $env_id);
  }
  return array(
    $auth_header,
    $nonce,
  );
}

/**
 * Returns the subscription's salt used to generate the derived key.
 *
 * The salt is stored in a system variable so that this module can continue
 * connecting to Acquia Search even when the subscription data is not available.
 * The most common reason for subscription data being unavailable is a failed
 * heartbeat connection to rpc.acquia.com.
 *
 * Acquia Connector versions <= 7.x-2.7 pulled the derived key salt directly
 * from the subscription data. In order to allow for seamless upgrades, this
 * function checks whether the system variable exists and sets it with the data
 * in the subscription if it doesn't.
 *
 * @return string
 *   The derived key salt.
 *
 * @see http://drupal.org/node/1784114
 */
function acquia_search_derived_key_salt() {
  $salt = variable_get('acquia_search_derived_key_salt', '');
  if (!$salt) {

    // If the variable doesn't exist, set it using the subscription data.
    $subscription = acquia_agent_settings('acquia_subscription_data');
    if (isset($subscription['derived_key_salt'])) {
      variable_set('acquia_search_derived_key_salt', $subscription['derived_key_salt']);
      $salt = $subscription['derived_key_salt'];
    }
  }
  return $salt;
}

/**
 * Get the derived key for the solr hmac using the information shared with acquia.com.
 */
function _acquia_search_derived_key($env_id = NULL) {
  static $derived_key = array();
  if (empty($env_id)) {
    $env_id = 0;
  }
  if (!isset($derived_key[$env_id])) {

    // If we set an explicit environment, check if this needs to overridden
    // Use the default
    $identifier = acquia_agent_settings('acquia_identifier');
    $key = acquia_agent_settings('acquia_key');

    // See if we need to overwrite these values
    if ($env_id) {

      // Load the explicit environment and a manually set search key.
      if ($search_key = apachesolr_environment_variable_get($env_id, 'acquia_search_key')) {
        $derived_key[$env_id] = $search_key;
      }
    }

    // In any case, this is equal for all subscriptions. Also
    // even if the search sub is different, the main subscription should be
    // active
    $derived_key_salt = acquia_search_derived_key_salt();

    // 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($derived_key_salt) || empty($key) || empty($identifier)) {

      // Expired or invalid subscription - don't continue.
      $derived_key[$env_id] = '';
    }
    elseif (!isset($derived_key[$env_id])) {

      // Get the Solr core identifier from the URL, to build the correct derived key.
      $environment = apachesolr_environment_load($env_id);
      if (isset($environment['url'])) {
        $core_id = acquia_search_extract_core_id_from_environment_url($environment['url']);
        $derived_key[$env_id] = _acquia_search_create_derived_key($derived_key_salt, $core_id, $key);
      }
    }
  }
  return $derived_key[$env_id];
}

/**
 * Extracts and return a core ID from given URL.
 *
 * Returns the last part of URL within given environment URL which represents
 * the core ID. E.g. for 'http://useast1-c1.acquia-search.com/solr/GHTV-36910'
 * it returns 'GHTV-36910'.
 * For 'http://useast1-c1.acquia-search.com/solr/GHTV-36910.dev.mysitedev'
 * it returns 'GHTV-36910.dev.mysitedev'.
 *
 * @param string $url
 *
 * @return string
 */
function acquia_search_extract_core_id_from_environment_url($url) {
  return substr($url, strrpos($url, '/') + 1);
}

/**
 * Derive a key for the solr hmac using a salt, id and key.
 */
function _acquia_search_create_derived_key($salt, $id, $key) {
  $derivation_string = $id . 'solr' . $salt;
  return hash_hmac('sha1', str_pad($derivation_string, 80, $derivation_string), $key);
}

/**
 * Creates an authenticator based on a data string and HMAC-SHA1.
 */
function acquia_search_authenticator($string, $nonce, $derived_key = NULL, $env_id = NULL, $time = NULL) {
  if (empty($derived_key)) {
    $derived_key = _acquia_search_derived_key($env_id);
  }
  if (empty($derived_key)) {

    // Expired or invalid subscription - don't continue.
    return '';
  }
  else {

    // @see http://stackoverflow.com/questions/2524680/check-whether-the-string-is-a-unix-timestamp
    if (!(is_numeric($time) && (int) $time == $time)) {

      // Use time() instead of REQUEST_TIME so that long-running operations like
      // `drush solr-index` continually have fresh request times. Use of
      // REQUEST_TIME will cause Acquia Search to respond with a 403 Forbidden
      // after the acquia_solr_time value is older than 15 minutes.
      $time = 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, $env_id = NULL) {
  if (empty($derived_key)) {
    $derived_key = _acquia_search_derived_key($env_id);
  }
  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($headers) {
  $reg = array();
  if (is_array($headers)) {
    foreach ($headers as $name => $value) {
      if (strtolower($name) == 'pragma' && preg_match("/hmac_digest=([^;]+);/i", $value, $reg)) {
        return trim($reg[1]);
      }
    }
  }
  return '';
}

/**
 * Implementation of hook_apachesolr_modify_query().
 *
 * Possibly alters the query type ('defType') param to edismax.
 */
function acquia_search_apachesolr_query_alter($query) {
  $environment = apachesolr_environment_load($query
    ->solr('getId'));

  // @todo - does it make sense to check $caller too?
  if (!acquia_search_environment_connected($environment) || $query
    ->getParam('qt') || $query
    ->getParam('defType')) {

    // This is a 'mlt' query or something else custom.
    return;
  }

  // Set the qt to edismax if we have keywords, and we always use it, or are
  // using a wildcard (* or ?).
  $keys = $query
    ->getParam('q');
  if ($keys && (($wildcard = preg_match('/\\S+[*?]/', $keys)) || variable_get('acquia_search_edismax_default', 0))) {
    $query
      ->addParam('defType', 'edismax');
    if ($wildcard) {
      $keys = preg_replace_callback('/(\\S+[*?]\\S*)/', '_acquia_search_lower', $keys);
      $query
        ->replaceParam('q', $keys);
    }
  }
}

/**
 * Convert to lower-case any keywords containing a wildcard.
 */
function _acquia_search_lower($matches) {
  return drupal_strtolower($matches[1]);
}

Functions

Namesort descending Description
acquia_search_acquia_subscription_status Implementation of hook_acquia_subscription_status().
acquia_search_add_apachesolr_overrides Overrides apachesolr configs to talk to the proper Acquia search core.
acquia_search_add_form_status_message Adds Acquia search connection details to the given form.
acquia_search_apachesolr_query_alter Implementation of hook_apachesolr_modify_query().
acquia_search_authenticator Creates an authenticator based on a data string and HMAC-SHA1.
acquia_search_auth_cookie Modify a solr base url and construct a hmac authenticator cookie.
acquia_search_available_cores Retrieves all available search cores as set in the subscription.
acquia_search_derived_key_salt Returns the subscription's salt used to generate the derived key.
acquia_search_disable Implementation of hook_disable().
acquia_search_disable_revert_defaults Helper function to revert the default environment after disable
acquia_search_enable Implementation of hook_enable().
acquia_search_enable_acquia_solr_environment Create a new record pointing to the Acquia apachesolr search server and set it as the default
acquia_search_environment_connected Tests whether the environment is connected to Acquia Search.
acquia_search_environment_delete_access Delete environment page access.
acquia_search_environment_edit_form_validate
acquia_search_extract_core_id_from_environment_url Extracts and return a core ID from given URL.
acquia_search_extract_hmac Look in the headers and get the hmac_digest out
acquia_search_form_apachesolr_environment_edit_form_alter Implementation of hook_form_[form_id]_alter().
acquia_search_form_apachesolr_index_action_form_alter Implementation of hook_form_[form_id]_alter().
acquia_search_form_apachesolr_settings_alter Implementation of hook_form_[form_id]_alter().
acquia_search_get_derived_key_for_core Returns derived key for the given core ID.
acquia_search_get_environment Predefined Acquia Search network environment
acquia_search_get_read_only_mode_warning Returns formatted message about read-only mode.
acquia_search_get_search_status_message Returns formatted message about Acquia Search connection details.
acquia_search_help Implementation of hook_help().
acquia_search_init Implements hook_init().
acquia_search_is_environment_overridden_to_read_only Determines whether the given environment is in the read-only mode.
acquia_search_menu_alter Implementation of hook_menu_alter().
acquia_search_ping Pings the search core.
acquia_search_valid_response Validate the authenticity of returned data using a nonce and HMAC-SHA1.
_acquia_search_create_derived_key Derive a key for the solr hmac using a salt, id and key.
_acquia_search_derived_key Get the derived key for the solr hmac using the information shared with acquia.com.
_acquia_search_lower Convert to lower-case any keywords containing a wildcard.
_acquia_search_set_version Helper function to cache the Acquia Search version.

Constants