search_api_acquia.module in Acquia Search for Search API 7.2
Same filename and directory in other branches
Provides integration between your Drupal site and Acquia's hosted search service via the Search API Solr module.
File
search_api_acquia.moduleView source
<?php
/**
* @file
* Provides integration between your Drupal site and Acquia's hosted search
* service via the Search API Solr module.
*/
use Drupal\search_api_acquia\SAPIPreferredSearchCoreService;
define('SEARCH_API_ACQUIA_OVERRIDE_AUTO_SET', 1);
define('SEARCH_API_ACQUIA_AUTO_OVERRIDE_READ_ONLY', 2);
define('SEARCH_API_ACQUIA_EXISTING_OVERRIDE', 3);
define('SEARCH_API_ACQUIA_AUTO_SHOULD_OVERRIDE_READ_ONLY', 4);
/**
* Implements hook_init().
*/
function search_api_acquia_init() {
$auto_switch_disabled = variable_get('acquia_search_disable_auto_switch', 0);
$subscription = acquia_agent_settings('acquia_subscription_data');
$sub_active = !empty($subscription['active']);
if (!$auto_switch_disabled && $sub_active && !module_exists('acquia_search_multi_subs')) {
$servers = search_api_server_load_multiple(FALSE, array(
'class' => 'acquia_search_service',
));
$result = search_api_acquia_add_solr_overrides($servers);
// Store result in global for this request. @see search_api_acquia_requirements()
$GLOBALS['conf']['search_api_acquia_init_result'] = $result;
}
}
/**
* Helper function that instantiates and caches the preferred search core service.
*/
function search_api_acquia_get_core_service($acquia_search_api_version = SearchApiAcquiaSearchService::ACQUIA_SEARCH_API_V2) {
static $core_services;
if (isset($core_services[$acquia_search_api_version])) {
return $core_services[$acquia_search_api_version];
}
if ($acquia_search_api_version === SearchApiAcquiaSearchService::ACQUIA_SEARCH_API_V3) {
$core_services[$acquia_search_api_version] = SearchApiAcquiaApi::getFromSettings()
->getPreferredCoreService();
return $core_services[$acquia_search_api_version];
}
module_load_include('php', 'search_api_acquia', 'src/SAPIPreferredSearchCoreService');
global $conf;
$acquia_identifier = acquia_agent_settings('acquia_identifier');
$ah_env = isset($_ENV['AH_SITE_ENVIRONMENT']) ? $_ENV['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 = search_api_acquia_available_cores($subscription);
$core_services[$acquia_search_api_version] = new SAPIPreferredSearchCoreService($acquia_identifier, $ah_env, $sites_foldername, $ah_db_name, $available_cores);
return $core_services[$acquia_search_api_version];
}
/**
* Retrieves all available search cores as set in the subscription.
*
* @param $subscription
* Acquia Subscription array.
*
* @return array|null
*/
function search_api_acquia_available_cores($subscription) {
if (!empty($subscription['heartbeat_data']['search_cores'])) {
return $subscription['heartbeat_data']['search_cores'];
}
return array();
}
/**
* Overrides search_api_solr 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 Search API 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 array $servers
* The Search API Solr servers.
*
* @return bool
* True if preferred core has been found in the list of available cores and
* it was used to override the search settings.
*/
function search_api_acquia_add_solr_overrides($servers) {
$override = FALSE;
/**
* @var string $acquia_server_id
* @var SearchApiServer $server
*/
foreach ($servers as $acquia_server_id => $server) {
$core_service = search_api_acquia_get_core_service($server
->getAcquiaSearchApiVersion());
$override |= search_api_acquia_override_server($core_service, $acquia_server_id);
}
return $override;
}
/**
* Overrides settings for a server.
*/
function search_api_acquia_override_server($core_service, $server_id) {
global $conf;
// Skip if the Search API server has already been overridden.
if (!empty($conf['search_api_acquia_overrides'][$server_id])) {
if (empty($conf['search_api_acquia_overrides'][$server_id]['overridden_by_acquia_search'])) {
$conf['search_api_acquia_overrides'][$server_id]['overridden_by_acquia_search'] = SEARCH_API_ACQUIA_EXISTING_OVERRIDE;
}
return FALSE;
}
$conf['search_api_acquia_overrides'][$server_id]['acquia_search_possible_cores'] = $core_service
->getListOfPossibleCores();
if ($core_service
->isPreferredCoreAvailable()) {
$derived_key = search_api_acquia_get_derived_key_for_core($core_service
->getPreferredCoreId());
$conf['search_api_acquia_overrides'][$server_id]['path'] = '/solr/' . $core_service
->getPreferredCoreId();
$conf['search_api_acquia_overrides'][$server_id]['host'] = $core_service
->getPreferredCoreHostname();
$conf['search_api_acquia_overrides'][$server_id]['derived_key'] = $derived_key;
$conf['search_api_acquia_overrides'][$server_id]['overridden_by_acquia_search'] = SEARCH_API_ACQUIA_OVERRIDE_AUTO_SET;
return TRUE;
}
// At this point none of the available search cores match to what is
// expected, so read-only mode should be set.
// Read only mode is actually set per-index during
// search_api_acquia_search_api_index_load(), however we add a diagnostic
// value here for testing.
if (!variable_get('acquia_search_disable_auto_read_only', 0)) {
$conf['search_api_acquia_overrides'][$server_id]['overridden_by_acquia_search'] = SEARCH_API_ACQUIA_AUTO_SHOULD_OVERRIDE_READ_ONLY;
return TRUE;
}
return FALSE;
}
/**
* Implements hook_search_api_index_load()
*
* This takes care of enforcing read-only mode, because that happens at the
* Search API index (and not at the server).
*
* @param array $indexes
* Array of Search API index entities.
*/
function search_api_acquia_search_api_index_load($indexes) {
global $conf;
$auto_switch_disabled = variable_get('acquia_search_disable_auto_switch', 0);
$read_only_switch_disabled = variable_get('acquia_search_disable_auto_read_only', 0);
$subscription = acquia_agent_settings('acquia_subscription_data');
$sub_active = !empty($subscription['active']);
if (!$auto_switch_disabled && !$read_only_switch_disabled && $sub_active && !module_exists('acquia_search_multi_subs')) {
foreach ($indexes as &$index) {
if (empty($index->server)) {
// This covers circumstances where the Acquia Search service hasn't
// been completely set up. Preventing an empty server machine name
// from loading prevents array_flip errors in entity_load that are
// otherwise hard to debug.
continue;
}
$server = search_api_server_load($index->server);
if (!method_exists($server, 'getAcquiaSearchApiVersion') || !search_api_acquia_get_core_service($server
->getAcquiaSearchApiVersion())
->isPreferredCoreAvailable()) {
continue;
}
if ($server && $server->class == 'acquia_search_service') {
$index->read_only = '1';
$conf['search_api_acquia_overrides'][$index->server]['overridden_by_acquia_search'] = SEARCH_API_ACQUIA_AUTO_OVERRIDE_READ_ONLY;
}
}
}
}
/**
* Returns derived key for the given core ID.
*
* @param string $core_id
*
* @return string
*/
function search_api_acquia_get_derived_key_for_core($core_id) {
$subscription = acquia_agent_settings('acquia_subscription_data');
$derived_key_salt = $subscription['derived_key_salt'] ?? '';
$key = acquia_agent_settings('acquia_key');
$derivation_string = $core_id . 'solr' . $derived_key_salt;
return hash_hmac('sha1', str_pad($derivation_string, 80, $derivation_string), $key);
}
/**
* Implements hook_form_[form_id]_alter().
*
* Adds extra information to some Search API edit forms.
* @param $form
* @param $form_state
*/
function search_api_acquia_form_search_api_admin_server_edit_alter(&$form, &$form_state) {
$server = $form_state['server'];
if ($server->class == 'acquia_search_service') {
if (!search_api_acquia_get_core_service($server
->getAcquiaSearchApiVersion())
->isPreferredCoreAvailable()) {
drupal_set_message(search_api_acquia_get_read_only_mode_warning($server), 'warning');
}
}
}
/**
* Implements hook_form_[form_id]_alter().
*
* Adds extra information to some Search API edit forms.
* @param array $form
* @param $form_state
*/
function search_api_acquia_form_search_api_admin_index_edit_alter(&$form, &$form_state) {
global $conf;
$server = search_api_index_get_server($form_state['index']);
if (is_object($server) && $server->class == 'acquia_search_service') {
search_api_acquia_add_form_status_message($form, $form_state, $server);
// Show message if in read-only mode.
if ($conf['search_api_acquia_overrides'][$server->machine_name]['overridden_by_acquia_search'] == SEARCH_API_ACQUIA_AUTO_OVERRIDE_READ_ONLY) {
drupal_set_message(search_api_acquia_get_read_only_mode_warning($server), 'warning');
}
}
}
/**
* Implements hook_block_view_MODULE_DELTA_alter().
*
* Add extra information onto some Search API admin pages.
*
* @param array $data
* @param array $block
*/
function search_api_acquia_block_view_system_main_alter(&$data, $block) {
global $conf;
if (isset($data['content']['view'])) {
$section = $data['content']['view'];
// #theme tells us what we're rendering right now.
if (!isset($section['#theme'])) {
return;
}
if ($section['#theme'] == 'search_api_server') {
$server = search_api_server_load($section['#machine_name']);
}
if ($section['#theme'] == 'search_api_index') {
$server = $section['#server'];
}
if (isset($server->class) && $server->class == 'acquia_search_service') {
$data['content']['acquia_search_message'] = array(
'#type' => 'fieldset',
'#title' => t('Acquia Search status for this connection'),
'#collapsible' => FALSE,
'#weight' => -10,
);
$data['content']['acquia_search_message']['message'] = array(
'#markup' => search_api_acquia_get_search_status_message($server),
);
if (isset($conf['search_api_acquia_overrides'][$server->machine_name]['overridden_by_acquia_search']) && $conf['search_api_acquia_overrides'][$server->machine_name]['overridden_by_acquia_search'] == SEARCH_API_ACQUIA_AUTO_OVERRIDE_READ_ONLY) {
drupal_set_message(search_api_acquia_get_read_only_mode_warning($server), 'warning');
}
}
}
}
/**
* Pings the search core.
*
* @param string $server_id
* Search API server id
* @param string $op
*
* @return bool
*/
function search_api_acquia_ping($server_id, $op = 'ping') {
$solr = search_api_server_load($server_id, true)
->getSolrConnection();
if (empty($solr)) {
return FALSE;
}
if ($op == 'ping') {
try {
return (bool) $solr
->{$op}();
} catch (Exception $e) {
watchdog_exception('search_api_acquia', $e, 'Exception thrown when calling @op on Search API Solr connection. %type: !message in %function (line %line of %file).', array(
'@op' => $op,
));
}
}
if ($op == 'deep-ping') {
try {
$result = $solr
->makeServletRequest('admin/luke', array(
'numTerms' => 0,
));
if ($result->code == 200) {
return TRUE;
}
} catch (Exception $e) {
watchdog_exception('search_api_acquia', $e, 'Exception thrown when calling @op on Search API Solr connection. %type: !message in %function (line %line of %file).', array(
'@op' => $op,
));
}
}
return FALSE;
}
/**
* Adds Acquia search connection details to the given form.
*
* @param array $form
* @param array $form_state
* @param SearchApiServer $server
*/
function search_api_acquia_add_form_status_message(&$form, &$form_state, $server) {
$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' => search_api_acquia_get_search_status_message($server),
);
}
/**
* Returns formatted message about read-only mode.
*
* @param SearchApiServer $server
* @param string $t
*
* @return string
*/
function search_api_acquia_get_read_only_mode_warning($server, $t = 't') {
global $conf;
$msg = $t('To protect your data, the Search API Acquia module is enforcing
read-only mode on the Search API indexes, 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($conf['search_api_acquia_overrides'][$server->machine_name]['acquia_search_possible_cores'])) {
$list = theme('item_list', array(
'items' => $conf['search_api_acquia_overrides'][$server->machine_name]['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 .= PHP_EOL . $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 SearchApiServer $server
* @return string
*/
function search_api_acquia_get_search_status_message($server) {
global $conf;
$options = $server->options;
// Apply overrides if they exist.
if (isset($conf['search_api_acquia_overrides'][$server->machine_name])) {
$options = array_merge($options, $conf['search_api_acquia_overrides'][$server->machine_name]);
}
$url = $options['scheme'] . '://' . $options['host'] . ':' . $options['port'] . $options['path'];
$items = array(
t('search_api_solr.module server ID: @env', array(
'@env' => $server->machine_name,
)),
t('URL: @url', array(
'@url' => $url,
)),
);
if (search_api_acquia_ping($server->machine_name)) {
$items[] = t('Solr index is currently reachable and up.');
}
else {
// Add message with error class.
$items[] = array(
'data' => t('Solr index is currently unreachable.'),
'class' => array(
'error',
),
);
}
// Deep-ping the Solr index to ensure authentication is working.
if (search_api_acquia_ping($server->machine_name, 'deep-ping')) {
$items[] = t('Requests to Solr index are passing authentication checks.');
}
else {
// Add message with error class.
$items[] = array(
'data' => t('Solr core authentication check fails.'),
'class' => array(
'error',
),
);
}
return t('Connection managed by Search API Acquia module.') . theme('item_list', array(
'items' => $items,
));
}
/**
* Implements hook_enable().
*/
function search_api_acquia_enable() {
search_api_acquia_set_version();
}
/**
* Implements hook_cron().
*/
function search_api_acquia_cron() {
search_api_acquia_set_version();
search_api_acquia_cron_optimize();
}
/**
* Runs optimize during cron runs.
*
* @see search_api_solr_cron()
*/
function search_api_acquia_cron_optimize() {
$action = variable_get('search_api_acquia_cron_action', 'spellcheck');
// We treat all unknown action settings as "none". However, we turn a blind
// eye for Britons and other people who can spell.
if (!in_array($action, array(
'spellcheck',
'optimize',
'optimise',
))) {
return;
}
// 86400 seconds is one day. We use slightly less here to allow for some
// variation in the request time of the cron run, so that the time of day will
// (more or less) stay the same.
if (REQUEST_TIME - variable_get('search_api_acquia_last_optimize', 0) > 86340) {
variable_set('search_api_acquia_last_optimize', REQUEST_TIME);
$conditions = array(
'class' => 'acquia_search_service',
'enabled' => TRUE,
);
$count = 0;
foreach (search_api_server_load_multiple(FALSE, $conditions) as $server) {
try {
$solr = $server
->getSolrConnection();
if ($action != 'spellcheck') {
$solr
->optimize(FALSE);
}
else {
$params['rows'] = 0;
$params['spellcheck'] = 'true';
$params['spellcheck.build'] = 'true';
$solr
->search(NULL, $params);
}
++$count;
} catch (SearchApiException $e) {
watchdog_exception('search_api_acquia', $e, '%type while optimizing Solr server @server: !message in %function (line %line of %file).', array(
'@server' => $server->name,
));
}
}
if ($count) {
$vars['@count'] = $count;
if ($action != 'spellcheck') {
watchdog('search_api_acquia', 'Optimized @count Solr server(s).', $vars, WATCHDOG_INFO);
}
else {
watchdog('search_api_acquia', 'Rebuilt spellcheck dictionary on @count Solr server(s).', $vars, WATCHDOG_INFO);
}
}
}
}
/**
* Sets the version of this module in a system variable.
*
* The version is used to construct the User Agent in requests to the backend.
* This allows Acquia to determine which module is being used to generate the
* search query which helps in debugging.
*/
function search_api_acquia_set_version() {
// Cache the version in a variable so we can send it at no extra cost.
$version = variable_get('search_api_acquia_version', '7.x');
$info = system_get_info('module', 'search_api_acquia');
// 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('search_api_acquia_version', $new_version);
}
}
/**
* Implements hook_search_api_service_info().
*/
function search_api_acquia_search_api_service_info() {
return array(
'acquia_search_service' => array(
'name' => t('Acquia Search'),
'description' => t('<p>Index items using the Acquia Search service.<p>'),
'class' => 'SearchApiAcquiaSearchService',
),
);
}