'Apache Solr search',
'description' => 'Administer Apache Solr.',
'page callback' => 'apachesolr_status_page',
'access arguments' => array('administer search'),
'weight' => -8,
'file' => 'apachesolr.admin.inc',
);
$items['admin/config/search/apachesolr/index'] = array(
'title' => 'Default index',
'description' => 'Administer Apache Solr.',
'page callback' => 'apachesolr_status_page',
'access arguments' => array('administer search'),
'weight' => -8,
'file' => 'apachesolr.admin.inc',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/config/search/apachesolr/settings'] = array(
'title' => 'Settings',
'weight' => 10,
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_settings'),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_LOCAL_TASK,
);
$settings_path = 'admin/config/search/apachesolr/settings/';
$items[$settings_path . '%apachesolr_environment/index'] = array(
'title' => 'Index',
'page callback' => 'apachesolr_status_page',
'page arguments' => array(5),
'access arguments' => array('administer search'),
'weight' => 0,
'file' => 'apachesolr.admin.inc',
'type' => MENU_LOCAL_TASK,
);
$items[$settings_path . '%apachesolr_environment/index/remaining'] = array(
'title' => 'Remaining',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_index_action_form_remaining_confirm', 5),
'file' => 'apachesolr.admin.inc',
'access arguments' => array('administer search'),
'type' => MENU_CALLBACK,
);
$items[$settings_path . '%apachesolr_environment/index/delete'] = array(
'title' => 'Reindex',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_index_action_form_delete_confirm', 5),
'file' => 'apachesolr.admin.inc',
'access arguments' => array('administer search'),
'type' => MENU_CALLBACK,
);
$items[$settings_path . '%apachesolr_environment/index/reset'] = array(
'title' => 'Reindex',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_index_action_form_reset_confirm', 5),
'file' => 'apachesolr.admin.inc',
'access arguments' => array('administer search'),
'type' => MENU_CALLBACK,
);
$items[$settings_path . '%apachesolr_environment/index/reset/confirm'] = array(
'title' => 'Confirm the re-indexing of all content',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_clear_index_confirm', 5),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_CALLBACK,
);
$items[$settings_path . '%apachesolr_environment/index/delete/confirm'] = array(
'title' => 'Confirm index deletion',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_delete_index_confirm', 5),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_CALLBACK,
);
$items[$settings_path . '%apachesolr_environment/edit'] = array(
'title' => 'Edit',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_environment_edit_form', 5),
'description' => 'Edit Apache Solr search environment.',
'access arguments' => array('administer search'),
'weight' => 10,
'file' => 'apachesolr.admin.inc',
'type' => MENU_LOCAL_TASK,
);
$items[$settings_path . '%apachesolr_environment/clone'] = array(
'title' => 'Apache Solr search environment clone',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_environment_clone_form', 5),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
);
$items[$settings_path . '%apachesolr_environment/delete'] = array(
'title' => 'Apache Solr search environment delete',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_environment_delete_form', 5),
'access callback' => 'apachesolr_environment_delete_page_access',
'access arguments' => array('administer search', 5),
'file' => 'apachesolr.admin.inc',
);
$items[$settings_path . 'add'] = array(
'title' => 'Add search environment',
'description' => 'Add Apache Solr environment.',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_environment_edit_form'),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_LOCAL_ACTION,
);
$items['admin/config/search/apachesolr/index/confirm/clear'] = array(
'title' => 'Confirm the re-indexing of all content',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_clear_index_confirm'),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_CALLBACK,
);
$items['admin/config/search/apachesolr/index/confirm/delete'] = array(
'title' => 'Confirm index deletion',
'page callback' => 'drupal_get_form',
'page arguments' => array('apachesolr_delete_index_confirm'),
'access arguments' => array('administer search'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_CALLBACK,
);
$reports_path = 'admin/reports/apachesolr';
$items[$reports_path] = array(
'title' => 'Apache Solr search index',
'description' => 'Information about the contents of the index on the server',
'page callback' => 'apachesolr_index_report',
'access arguments' => array('access site reports'),
'file' => 'apachesolr.admin.inc',
);
$items[$reports_path . '/%apachesolr_environment'] = array(
'title' => 'Apache Solr search index',
'description' => 'Information about the contents of the index on the server',
'page callback' => 'apachesolr_index_report',
'page arguments' => array(3),
'access arguments' => array('access site reports'),
'file' => 'apachesolr.admin.inc',
);
$items[$reports_path . '/%apachesolr_environment/index'] = array(
'title' => 'Search index',
'file' => 'apachesolr.admin.inc',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[$reports_path . '/%apachesolr_environment/conf'] = array(
'title' => 'Configuration files',
'page callback' => 'apachesolr_config_files_overview',
'access arguments' => array('access site reports'),
'file' => 'apachesolr.admin.inc',
'weight' => 5,
'type' => MENU_LOCAL_TASK,
);
$items[$reports_path . '/%apachesolr_environment/conf/%'] = array(
'title' => 'Configuration file',
'page callback' => 'apachesolr_config_file',
'page arguments' => array(5, 3),
'access arguments' => array('access site reports'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_CALLBACK,
);
if (module_exists('devel')) {
$items['node/%node/devel/apachesolr'] = array(
'title' => 'Apache Solr',
'page callback' => 'apachesolr_devel',
'page arguments' => array(1),
'access arguments' => array('access devel information'),
'file' => 'apachesolr.admin.inc',
'type' => MENU_LOCAL_TASK,
);
}
// We handle our own menu paths for facets
if (module_exists('facetapi')) {
$file_path = drupal_get_path('module', 'facetapi');
$first = TRUE;
foreach (facetapi_get_realm_info() as $realm_name => $realm) {
if ($first) {
$first = FALSE;
$items[$settings_path . '%apachesolr_environment/facets'] = array(
'title' => 'Facets',
'page callback' => 'apachesolr_enabled_facets_page',
'page arguments' => array($realm_name, 5),
'weight' => -5,
'access arguments' => array('administer search'),
'file path' => $file_path,
'file' => 'facetapi.admin.inc',
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
}
else {
$items[$settings_path . '%apachesolr_environment/facets/' . $realm_name] = array(
'title' => $realm['label'],
'page callback' => 'apachesolr_enabled_facets_page',
'page arguments' => array($realm_name, 5),
'weight' => -5,
'access arguments' => array('administer search'),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
'file path' => $file_path,
'file' => 'facetapi.admin.inc',
);
}
}
}
return $items;
}
/**
* Wrapper for facetapi settings forms.
*/
function apachesolr_enabled_facets_page($realm_name, $environment = NULL) {
$page = array();
if (isset($environment['env_id'])) {
$env_id = $environment['env_id'];
}
else {
$env_id = apachesolr_default_environment();
}
$searcher = 'apachesolr@' . $env_id;
// Initializes output with information about which environment's setting we are
// editing, as it is otherwise not transparent to the end user.
$page['apachesolr_environment'] = array(
'#theme' => 'apachesolr_settings_title',
'#env_id' => $env_id,
);
$page['settings'] = drupal_get_form('facetapi_realm_settings_form', $searcher, $realm_name);
return $page;
}
/**
* Implements hook_facetapi_searcher_info().
*/
function apachesolr_facetapi_searcher_info() {
$info = array();
// TODO: is it needed to return all of them here?
foreach (apachesolr_load_all_environments() as $id => $environment) {
$info['apachesolr@' . $id] = array(
'label' => t('Apache Solr environment: @environment', array('@environment' => $environment['name'])),
'adapter' => 'apachesolr',
'instance' => $id,
'path' => '',
'supports facet mincount' => TRUE,
'supports facet missing' => TRUE,
'include default facets' => FALSE,
);
}
return $info;
}
/**
* Implements hook_facetapi_adapters().
*/
function apachesolr_facetapi_adapters() {
return array(
'apachesolr' => array(
'handler' => array(
'class' => 'ApacheSolrFacetapiAdapter',
),
),
);
}
/**
* Implements hook_facetapi_query_types().
*/
function apachesolr_facetapi_query_types() {
return array(
'apachesolr_term' => array(
'handler' => array(
'class' => 'ApacheSolrFacetapiTerm',
'adapter' => 'apachesolr',
),
),
'apachesolr_date' => array(
'handler' => array(
'class' => 'ApacheSolrFacetapiDate',
'adapter' => 'apachesolr',
),
),
'apachesolr_numeric_range' => array(
'handler' => array(
'class' => 'ApacheSolrFacetapiNumericRange',
'adapter' => 'apachesolr',
),
),
'apachesolr_geo' => array(
'handler' => array(
'class' => 'ApacheSolrFacetapiGeo',
'adapter' => 'apachesolr',
),
),
);
}
/**
* Implements hook_facetapi_facet_info().
* Currently it only supports the node entity type
*/
function apachesolr_facetapi_facet_info($searcher_info) {
$facets = array();
if ('apachesolr' == $searcher_info['adapter']) {
$environment = apachesolr_environment_load($searcher_info['instance']);
if (!empty($environment['conf']['facet callbacks'])) {
foreach ($environment['conf']['facet callbacks'] as $callback) {
if (is_callable($callback)) {
$facets = array_merge($facets, call_user_func($callback, $searcher_info));
}
}
}
elseif (isset($searcher_info['types']['node'])) {
$facets = apachesolr_default_node_facet_info();
}
}
return $facets;
}
/**
* Returns an array of facets for node fields and attributes.
*
* @return
* An array of node facets.
*/
function apachesolr_default_node_facet_info() {
return array_merge(apachesolr_common_node_facets(), apachesolr_entity_field_facets('node'));
}
/**
* Returns an array of facets for the provided entity type's fields.
*
* @param string $entity_type
* An entity type machine name.
* @return
* An array of facets for the fields of the requested entity type.
*/
function apachesolr_entity_field_facets($entity_type) {
$facets = array();
foreach (apachesolr_entity_fields($entity_type) as $field_nm => $entity_fields) {
foreach ($entity_fields as $field_info) {
if (!empty($field_info['facets'])) {
$field = apachesolr_index_key($field_info);
$facets[$field] = array(
'label' => check_plain($field_info['display_name']),
'dependency plugins' => $field_info['dependency plugins'],
'field api name' => $field_info['field']['field_name'],
'description' => t('Filter by field @field of type @type.', array(
'@type' => $field_info['field']['type'],
'@field' => $field_info['field']['field_name'],
)),
'map callback' => $field_info['map callback'],
'map options' => $field_info,
'hierarchy callback' => $field_info['hierarchy callback'],
);
if (!empty($field_info['facet mincount allowed'])) {
$facets[$field]['facet mincount allowed'] = $field_info['facet mincount allowed'];
}
if (!empty($field_info['facet missing allowed'])) {
$facets[$field]['facet missing allowed'] = $field_info['facet missing allowed'];
}
if (!empty($field_info['query types'])) {
$facets[$field]['query types'] = $field_info['query types'];
}
if (!empty($field_info['allowed operators'])) {
$facets[$field]['allowed operators'] = $field_info['allowed operators'];
}
// TODO : This is actually deprecated but we should still support
// older versions of facetapi. We should remove once facetapi has RC1
// For reference : http://drupal.org/node/1161444
if (!empty($field_info['query type'])) {
$facets[$field]['query type'] = $field_info['query type'];
}
if (!empty($field_info['min callback'])) {
$facets[$field]['min callback'] = $field_info['min callback'];
}
if (!empty($field_info['max callback'])) {
$facets[$field]['max callback'] = $field_info['max callback'];
}
if (!empty($field_info['map callback'])) {
$facets[$field]['map callback'] = $field_info['map callback'];
}
if (!empty($field_info['alter callbacks'])) {
$facets[$field]['alter callbacks'] = $field_info['alter callbacks'];
}
}
}
}
return $facets;
}
/**
* Helper function returning common facet definitions.
*/
function apachesolr_common_node_facets() {
$facets['bundle'] = array(
'label' => t('Content type'),
'description' => t('Filter by content type.'),
'field api bundles' => array('node'),
'map callback' => 'facetapi_map_bundle',
'values callback' => 'facetapi_callback_type_values',
'facet mincount allowed' => TRUE,
'dependency plugins' => array('role'),
);
$facets['author'] = array(
'label' => t('Author'),
'description' => t('Filter by author.'),
'field' => 'is_uid',
'map callback' => 'facetapi_map_author',
'values callback' => 'facetapi_callback_user_values',
'facet mincount allowed' => TRUE,
'dependency plugins' => array('bundle', 'role'),
);
$facets['language'] = array(
'label' => t('Language'),
'description' => t('Filter by language.'),
'field' => 'ss_language',
'map callback' => 'facetapi_map_language',
'values callback' => 'facetapi_callback_language_values',
'facet mincount allowed' => TRUE,
'dependency plugins' => array('bundle', 'role'),
);
$facets['created'] = array(
'label' => t('Post date'),
'description' => t('Filter by the date the node was posted.'),
'field' => 'ds_created',
'query types' => array('date'),
'allowed operators' => array(FACETAPI_OPERATOR_AND => TRUE),
'map callback' => 'facetapi_map_date',
'min callback' => 'facetapi_get_min_date',
'max callback' => 'facetapi_get_max_date',
'dependency plugins' => array('bundle', 'role'),
'default sorts' => array(
array('active', SORT_DESC),
array('indexed', SORT_ASC),
),
);
$facets['changed'] = array(
'label' => t('Updated date'),
'description' => t('Filter by the date the node was last modified.'),
'field' => 'ds_changed',
'query types' => array('date'),
'allowed operators' => array(FACETAPI_OPERATOR_AND => TRUE),
'map callback' => 'facetapi_map_date',
'min callback' => 'facetapi_get_min_date',
'max callback' => 'facetapi_get_max_date',
'dependency plugins' => array('bundle', 'role'),
'default sorts' => array(
array('active', SORT_DESC),
array('indexed', SORT_ASC),
),
);
if (module_exists('book')) {
$facets['book'] = array(
'label' => t('Book'),
'description' => t('Filter by the book that the node belongs to.'),
'field' => 'is_book_bid',
'map callback' => 'apachesolr_map_book',
'facet mincount allowed' => TRUE,
'dependency plugins' => array('bundle', 'role'),
);
}
return $facets;
}
/**
* FacetAPI mapping callback.
*/
function apachesolr_map_book(array $values) {
$map = array();
if (!empty($values)) {
foreach (book_get_books() as $bid => $book) {
if (in_array($bid, $values)) {
$map[$bid] = $book['title'];
}
}
}
return $map;
}
/**
* Implements hook_form_[form_id]_alter().
*
* Mark a node for re-indexing when the book outline form is saved.
*/
function apachesolr_form_book_outline_form_alter(&$form, $form_state) {
$form['#submit'][] = 'apachesolr_mark_book_outline_node';
}
/**
* Submit handler for the book outline form.
*
* Marks the node for re-indexing.
*/
function apachesolr_mark_book_outline_node($form, $form_state) {
apachesolr_mark_entity('node', $form['#node']->nid);
}
/**
* Determines Apache Solr's behavior when searching causes an exception (e.g. Solr isn't available.)
* Depending on the admin settings, possibly redirect to Drupal's core search.
*
* @param $search_name
* The name of the search implementation.
*
* @param $querystring
* The search query that was issued at the time of failure.
*/
function apachesolr_failure($search_name, $querystring) {
$fail_rule = variable_get('apachesolr_failure', 'apachesolr:show_error');
switch ($fail_rule) {
case 'apachesolr:show_error':
drupal_set_message(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), 'error');
break;
case 'apachesolr:show_no_results':
// Do nothing.
break;
default:
// If we're failing over to another module make sure the search is available.
if (module_exists('search')) {
$search_info = search_get_info();
if (isset($search_info[$fail_rule])) {
$search_info = $search_info[$fail_rule];
drupal_set_message(t("%search_name is not available. Your search is being redirected.", array('%search_name' => $search_name)));
drupal_goto('search/' . $search_info['path'] . '/' . rawurlencode($querystring));
}
}
// if search is not enabled, break and do nothing
break;
}
}
/**
* Like $site_key in _update_refresh() - returns a site-specific hash.
*/
function apachesolr_site_hash() {
if (!($hash = variable_get('apachesolr_site_hash', FALSE))) {
global $base_url;
// Set a random 6 digit base-36 number as the hash.
$hash = substr(base_convert(sha1(uniqid($base_url, TRUE)), 16, 36), 0, 6);
variable_set('apachesolr_site_hash', $hash);
}
return $hash;
}
/**
* Generate a unique ID for an entity being indexed.
*
* @param $id
* An id number (or string) unique to this site, such as a node ID.
* @param $entity
* A string like 'node', 'file', 'user', or some other Drupal object type.
*
* @return
* A string combining the parameters with the site hash.
*/
function apachesolr_document_id($id, $entity_type = 'node') {
return apachesolr_site_hash() . "/{$entity_type}/" . $id;
}
/**
* Mark one entity as needing re-indexing.
*/
function apachesolr_mark_entity($entity_type, $entity_id) {
module_load_include('inc', 'apachesolr', 'apachesolr.index');
$table = apachesolr_get_indexer_table($entity_type);
if (!empty($table)) {
db_update($table)
->condition('entity_id', $entity_id)
->fields(array('changed' => REQUEST_TIME))
->execute();
}
}
/**
* Implements hook_user_update().
*
* Mark nodes as needing re-indexing if the author name changes.
*
* @see http://drupal.org/node/592522
* Performance issue with Mysql
* @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
* To know why PDO in drupal does not support UPDATE and JOIN at once.
*/
function apachesolr_user_update(&$edit, $account, $category) {
if (isset($account->name) && isset($account->original) && isset($account->original->name) && $account->name != $account->original->name) {
$table = apachesolr_get_indexer_table('node');
switch (db_driver()) {
case 'mysql' :
$table = db_escape_table($table);
$query = "UPDATE {{$table}} asn
INNER JOIN {node} n ON asn.entity_id = n.nid SET asn.changed = :changed
WHERE n.uid = :uid";
$result = db_query($query, array(':changed' => REQUEST_TIME,
':uid' => $account->uid,
));
break;
default :
$nids = db_select('node')
->fields('node', array('nid'))
->where("uid = :uid", array(':uid' => $account->uid));
$update = db_update($table)
->condition('entity_id', $nids, 'IN')
->fields(array('changed' => REQUEST_TIME))
->execute();
}
}
}
/**
* Implements hook_term_update().
*
* Mark nodes as needing re-indexing if a term name changes.
*
* @see http://drupal.org/node/592522
* Performance issue with Mysql
* @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
* To know why PDO in drupal does not support UPDATE and JOIN at once.
* @todo the rest, such as term deletion.
*/
function apachesolr_taxonomy_term_update($term) {
$table = apachesolr_get_indexer_table('node');
switch (db_driver()) {
case 'mysql' :
$table = db_escape_table($table);
$query = "UPDATE {{$table}} asn
INNER JOIN {taxonomy_index} ti ON asn.entity_id = ti.nid SET asn.changed = :changed
WHERE ti.tid = :tid";
$result = db_query($query, array(':changed' => REQUEST_TIME,
':tid' => $term->tid,
));
break;
default :
$nids = db_select('taxonomy_index')
->fields('taxonomy_index', array('nid'))
->where("tid = :tid", array(':tid' => $term->tid));
$update = db_update($table)
->condition('entity_id', $nids, 'IN')
->fields(array('changed' => REQUEST_TIME))
->execute();
}
}
/**
* Implement hook_comment_*().
*
* Mark nodes as needing re-indexing if comments are added or changed.
* Like search_comment().
*/
/**
* Implements hook_comment_insert().
*/
function apachesolr_comment_insert($comment) {
apachesolr_mark_entity('node', $comment->nid);
}
/**
* Implements hook_comment_update().
*/
function apachesolr_comment_update($comment) {
apachesolr_mark_entity('node', $comment->nid);
}
/**
* Implements hook_comment_delete().
*/
function apachesolr_comment_delete($comment) {
apachesolr_mark_entity('node', $comment->nid);
}
/**
* Implements hook_comment_publish().
*/
function apachesolr_comment_publish($comment) {
apachesolr_mark_entity('node', $comment->nid);
}
/**
* Implements hook_comment_unpublish().
*/
function apachesolr_comment_unpublish($comment) {
apachesolr_mark_entity('node', $comment->nid);
}
/**
* Implements hook_node_type_delete().
*/
function apachesolr_node_type_delete($info) {
module_load_include('inc', 'apachesolr', 'apachesolr.index');
$env_id = apachesolr_default_environment();
$existing_bundles = apachesolr_get_index_bundles($env_id, 'node');
$new_bundles = $existing_bundles;
$index = array_search($info->type, $existing_bundles);
if ($index !== FALSE) {
unset($new_bundles[$index]);
$new_bundles = array_values($new_bundles);
apachesolr_index_set_bundles($env_id, 'node', $new_bundles);
}
apachesolr_index_delete_bundles($env_id, 'node', array($info->type));
$bundles_changed_callback = apachesolr_entity_get_callback('node', 'bundles changed callback');
if (!empty($bundles_changed_callback)) {
call_user_func($bundles_changed_callback, $env_id, $existing_bundles, $new_bundles);
}
apachesolr_environments_clear_cache();
}
/**
* Implements hook_node_type_update().
*
* @see http://drupal.org/node/592522
* Performance issue with Mysql
* @see http://api.drupal.org/api/drupal/includes--database--database.inc/function/db_update/7#comment-15459
* To know why PDO in drupal does not support UPDATE and JOIN at once.
* @todo Support backwards compatibility
*/
function apachesolr_node_type_update($info) {
if (!empty($info->old_type) && $info->old_type != $info->type) {
// We cannot be sure we are going before or after node module.
$table = apachesolr_get_indexer_table('node');
switch (db_driver()) {
case 'mysql' :
$table = db_escape_table($table);
$query = "UPDATE {{$table}} asn
INNER JOIN {node} n ON asn.entity_id = n.nid SET asn.changed = :changed
WHERE (n.type = :type OR n.type = :old_type)";
$result = db_query($query, array(':changed' => REQUEST_TIME,
':type' => $info->type,
':old_type' => $info->old_type,
));
break;
default :
$nids = db_select('node')
->fields('node', array('nid'))
->where("type = :new OR type = :old", array(':new' => $info->type, ':old' => $info->old_type));
$update = db_update($table)
->condition('entity_id', $nids, 'IN')
->fields(array('changed' => REQUEST_TIME))
->execute();
}
db_update('apachesolr_index_bundles')
->condition('bundle', $info->old_type)
->condition('entity_type', 'node')
->fields(array('bundle' => $info->type))
->execute();
apachesolr_environments_clear_cache();
}
}
/**
* Implements hook_node_type_insert().
*
* Insert our new type into all the environments as indexable bundle type
* @param array $info
*/
function apachesolr_node_type_insert($info) {
module_load_include('inc', 'apachesolr', 'apachesolr.index');
// Get all our environments
$envs = apachesolr_load_all_environments();
$bundles = array();
foreach($envs as $env) {
if (isset($env['index_bundles']['node'])) {
$bundles = $env['index_bundles']['node'];
}
// Is the bundle already marked?
if (!in_array($info->type, $bundles)) {
$bundles[] = $info->type;
// Set the new bundle as indexable for all environments
apachesolr_index_set_bundles($env['env_id'], 'node', $bundles);
}
}
}
/**
* Convert date from timestamp into ISO 8601 format.
* http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html
*/
function apachesolr_date_iso($date_timestamp) {
return gmdate('Y-m-d\TH:i:s\Z', $date_timestamp);
}
/**
* Function to flatten documents array recursively.
*
* @param array $documents
* The array of documents being indexed.
* @param array &$tmp
* A container variable that will contain the flattened array.
*/
function apachesolr_flatten_documents_array($documents, &$tmp) {
foreach ($documents AS $index => $item) {
if (is_array($item)) {
apachesolr_flatten_documents_array($item, $tmp);
}
elseif (is_object($item)) {
$tmp[] = $item;
}
}
}
/**
* Implements hook_flush_caches().
*/
function apachesolr_flush_caches() {
return array('cache_apachesolr');
}
/**
* A wrapper for cache_clear_all to be used as a submit handler on forms that
* require clearing Luke cache etc.
*/
function apachesolr_clear_cache($env_id) {
// Reset $env_id to NULL if call originates from a form submit handler.
if (is_array($env_id)) {
$env_id = NULL;
}
try {
$solr = apachesolr_get_solr($env_id);
$solr->clearCache();
}
catch (Exception $e) {
watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
drupal_set_message(nl2br(check_plain($e->getMessage())), 'warning');
}
}
/**
* Call drupal_set_message() with the text.
*
* The text is translated with t() and substituted using Solr stats.
* @todo This is not according to drupal code standards
*/
function apachesolr_set_stats_message($text, $type = 'status', $repeat = FALSE) {
try {
$solr = apachesolr_get_solr();
$stats_summary = $solr->getStatsSummary();
drupal_set_message(check_plain(t($text, $stats_summary)), $type, FALSE);
}
catch (Exception $e) {
watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
}
}
/**
* Returns last changed and last ID for an environment and entity type.
*/
function apachesolr_get_last_index_position($env_id, $entity_type) {
$stored = apachesolr_environment_variable_get($env_id, 'apachesolr_index_last', array());
return isset($stored[$entity_type]) ? $stored[$entity_type] : array('last_changed' => 0, 'last_entity_id' => 0);
}
/**
* Sets last changed and last ID for an environment and entity type.
*/
function apachesolr_set_last_index_position($env_id, $entity_type, $last_changed, $last_entity_id) {
$stored = apachesolr_environment_variable_get($env_id, 'apachesolr_index_last', array());
$stored[$entity_type] = array('last_changed' => $last_changed, 'last_entity_id' => $last_entity_id);
apachesolr_environment_variable_set($env_id, 'apachesolr_index_last', $stored);
}
/**
* Clear a specific environment, or clear all.
*/
function apachesolr_clear_last_index_position($env_id = NULL, $entity_type = NULL) {
if (!empty($env_id)) {
$stored = apachesolr_environment_variable_get($env_id, 'apachesolr_index_last', array());
if ($entity_type) {
unset($stored[$entity_type]);
}
else {
$stored = array();
}
apachesolr_environment_variable_set($env_id, 'apachesolr_index_last', $stored);
}
else {
$environments = apachesolr_load_all_environments();
foreach (array_keys($environments) as $env_id) {
apachesolr_environment_variable_set($env_id, 'apachesolr_index_last', array());
}
}
}
/**
* Set the timestamp of the last index update
* @param $timestamp
* A timestamp or zero. If zero, the variable is deleted.
*/
function apachesolr_set_last_index_updated($env_id, $timestamp = 0) {
apachesolr_environment_variable_set($env_id, 'apachesolr_index_updated', $timestamp);
}
/**
* Get the timestamp of the last index update.
* @return integer (timestamp)
*/
function apachesolr_get_last_index_updated($env_id) {
return apachesolr_environment_variable_get($env_id, 'apachesolr_index_updated', 0);
}
/**
* Implements hook_cron().
* Runs the indexing process on all writable environments or just a given environment.
*/
function apachesolr_cron($env_id = NULL) {
$environments = array();
if (empty($env_id)) {
$environments = array_keys(apachesolr_load_all_environments());
}
else {
$environments[] = $env_id;
}
module_load_include('inc', 'apachesolr', 'apachesolr.index');
// Optimize the index (by default once a day).
$optimize_interval = variable_get('apachesolr_optimize_interval', 60 * 60 * 24);
$time = REQUEST_TIME;
foreach($environments as $env_id) {
$last = apachesolr_environment_variable_get($env_id, 'apachesolr_last_optimize', 0);
// Indexes in read-only mode do not change the index, so will not update, delete, or optimize during cron.
if (apachesolr_environment_variable_get($env_id, 'apachesolr_read_only', APACHESOLR_READ_WRITE) == APACHESOLR_READ_ONLY) {
continue;
}
// For every entity type that requires extra validation
foreach (entity_get_info() as $type => $info) {
$bundles = apachesolr_get_index_bundles($env_id, $type);
// If we're not checking any bundles of this entity type, just skip them all.
if (empty($bundles)) {
continue;
}
if (isset($info['apachesolr']['cron_check'])) {
$callback = $info['apachesolr']['cron_check'];
call_user_func($callback);
}
}
try {
$solr = apachesolr_get_solr($env_id);
if ($optimize_interval && ($time - $last > $optimize_interval)) {
$solr->optimize(FALSE, FALSE);
apachesolr_environment_variable_set($env_id, 'apachesolr_last_optimize', $time);
apachesolr_set_last_index_updated($env_id, $time);
}
// Only clear the cache if the index changed.
// TODO: clear on some schedule if running multi-site.
$updated = apachesolr_get_last_index_updated($env_id);
if ($updated > 0) {
$solr->clearCache();
// Re-populate the luke cache.
$solr->getLuke();
// TODO: an admin interface for setting this. Assume for now 5 minutes.
if ($time - $updated >= variable_get('apachesolr_cache_delay', 300)) {
// Clear the updated flag.
apachesolr_set_last_index_updated($env_id);
}
}
}
catch (Exception $e) {
watchdog('Apache Solr', nl2br(check_plain($e->getMessage())) . ' in apachesolr_cron', NULL, WATCHDOG_ERROR);
}
// We can safely process the apachesolr_cron_limit nodes at a time without a
// timeout or out of memory error.
$limit = variable_get('apachesolr_cron_limit', 50);
apachesolr_index_entities($env_id, $limit);
}
}
/**
* Implements hook_form_[form_id]_alter().
*
* Make sure to flush cache when content types are changed.
*/
function apachesolr_form_node_type_form_alter(&$form, $form_state) {
$form['#submit'][] = 'apachesolr_clear_cache';
}
/**
* Implements hook_form_[form_id]_alter(). (D7)
*
* Make sure to flush cache when fields are added.
*/
function apachesolr_form_field_ui_field_overview_form_alter(&$form, $form_state) {
$form['#submit'][] = 'apachesolr_clear_cache';
}
/**
* Implements hook_form_[form_id]_alter(). (D7)
*
* Make sure to flush cache when fields are updated.
*/
function apachesolr_form_field_ui_field_edit_form_alter(&$form, $form_state) {
$form['#submit'][] = 'apachesolr_clear_cache';
}
/**
* Sets breadcrumb trails for Facet API settings forms.
*
* @param FacetapiAdapter $adapter
* The Facet API adapter object.
* @param array $realm
* The realm definition.
*/
function apachesolr_set_facetapi_breadcrumb(FacetapiAdapter $adapter, array $realm) {
if ('apachesolr' == $adapter->getId()) {
// Hack here that depnds on our construction of the searcher name in this way.
list(, $env_id) = explode('@', $adapter->getSearcher());
// Appends additional breadcrumb items.
$breadcrumb = drupal_get_breadcrumb();
$breadcrumb[] = l(t('Apache Solr search environment edit'), 'admin/config/search/apachesolr/settings/' . $env_id);
$breadcrumb[] = l($realm['label'], 'admin/config/search/apachesolr/settings/' . $env_id . '/facets/' . $realm['name']);
drupal_set_breadcrumb($breadcrumb);
}
}
/**
* Implements hook_form_[form_id]_alter(). (D7)
*/
function apachesolr_form_facetapi_facet_settings_form_alter(&$form, $form_state) {
apachesolr_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']);
}
/**
* Implements hook_form_[form_id]_alter(). (D7)
*/
function apachesolr_form_facetapi_facet_dependencies_form_alter(&$form, $form_state) {
apachesolr_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']);
}
/**
* Semaphore that indicates whether a search has been done. Blocks use this
* later to decide whether they should load or not.
*
* @param $searched
* A boolean indicating whether a search has been executed.
*
* @return
* TRUE if a search has been executed.
* FALSE otherwise.
*/
function apachesolr_has_searched($env_id, $searched = NULL) {
$_searched = &drupal_static(__FUNCTION__, FALSE);
if (is_bool($searched)) {
$_searched[$env_id] = $searched;
}
// Return false if the search environment is not available in our array
if (!isset($_searched[$env_id])) {
return FALSE;
}
return $_searched[$env_id];
}
/**
* Semaphore that indicates whether Blocks should be suppressed regardless
* of whether a search has run.
*
* @param $suppress
* A boolean indicating whether to suppress.
*
* @return
* TRUE if a search has been executed.
* FALSE otherwise.
*/
function apachesolr_suppress_blocks($env_id, $suppress = NULL) {
$_suppress = &drupal_static(__FUNCTION__, FALSE);
if (is_bool($suppress)) {
$_suppress[$env_id] = $suppress;
}
// Return false if the search environment is not available in our array
if (!isset($_suppress[$env_id])) {
return FALSE;
}
return $_suppress[$env_id];
}
/**
* Get or set the default environment ID for the current page.
*/
function apachesolr_default_environment($env_id = NULL) {
$default_env_id = &drupal_static(__FUNCTION__, NULL);
if (isset($env_id)) {
$default_env_id = $env_id;
}
if (empty($default_env_id)) {
$default_env_id = variable_get('apachesolr_default_environment', 'solr');
}
return $default_env_id;
}
/**
* Set the default environment and let other modules know about the change.
*/
function apachesolr_set_default_environment($env_id) {
$old_env_id = variable_get('apachesolr_default_environment', 'solr');
variable_set('apachesolr_default_environment', $env_id);
module_invoke_all('apachesolr_default_environment', $env_id, $old_env_id);
}
/**
* Factory method for solr singleton objects. Structure allows for an arbitrary
* number of solr objects to be used based on a name whie maps to
* the host, port, path combination.
* Get an instance like this:
* try {
* $solr = apachesolr_get_solr();
* }
* catch (Exception $e) {
* watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
* }
*
*
* @param string $env_id
*
* @return DrupalApacheSolrServiceInterface $solr
*
* @throws Exception
*/
function apachesolr_get_solr($env_id = NULL) {
$solr_cache = &drupal_static(__FUNCTION__);
$environments = apachesolr_load_all_environments();
if (!interface_exists('DrupalApacheSolrServiceInterface')) {
require_once(dirname(__FILE__) . '/apachesolr.interface.inc');
}
if (empty($env_id)) {
$env_id = apachesolr_default_environment();
}
elseif (empty($environments[$env_id])) {
throw new Exception(t('Invalid Apache Solr environment: @env_id.', array('@env_id' => $env_id)));
}
if (isset($environments[$env_id])) {
$class = $environments[$env_id]['service_class'];
if (empty($solr_cache[$env_id])) {
// Use the default class if none is specified.
if (empty($class)) {
$class = variable_get('apachesolr_service_class', 'DrupalApacheSolrService');
}
// Takes advantage of auto-loading.
$solr = new $class($environments[$env_id]['url'], $env_id);
$soft_commit = apachesolr_environment_variable_get($env_id, 'apachesolr_soft_commit', FALSE);
if ($soft_commit) {
$solr->setSoftCommit($soft_commit);
}
$solr_cache[$env_id] = $solr;
}
return $solr_cache[$env_id];
}
else {
throw new Exception('No default Apache Solr environment.');
}
}
/**
* Function that loads all the environments
*
* @return $environments
* The environments in the database
*/
function apachesolr_load_all_environments() {
$environments = &drupal_static(__FUNCTION__);
if (isset($environments)) {
return $environments;
}
// Use cache_get to avoid DB when using memcache, etc.
$cache = cache_get('apachesolr:environments', 'cache_apachesolr');
if (isset($cache->data)) {
$environments = $cache->data;
}
elseif (!db_table_exists('apachesolr_index_bundles') || !db_table_exists('apachesolr_environment')) {
// Sometimes this function is called when the 'apachesolr_index_bundles' is
// not created yet.
$environments = array();
}
else {
// If ctools is available use its crud functions to load the environments.
if (module_exists('ctools')) {
ctools_include('export');
$environments = ctools_export_load_object('apachesolr_environment', 'all');
// Convert environments to array.
foreach ($environments as &$environment) {
$environment = (array) $environment;
}
}
else {
$environments = db_query('SELECT * FROM {apachesolr_environment}')->fetchAllAssoc('env_id', PDO::FETCH_ASSOC);
}
// Load conf and index bundles. We don't use 'subrecords callback' property
// of ctools export API.
apachesolr_environment_load_subrecords($environments);
cache_set('apachesolr:environments', $environments, 'cache_apachesolr');
}
// Allow overrides of environments from settings.php
$conf_environments = variable_get('apachesolr_environments', array());
if (!empty($conf_environments)) {
$environments = drupal_array_merge_deep($environments, $conf_environments);
}
return $environments;
}
/**
* Function that loads an environment
*
* @param $env_id
* The environment ID it needs to load.
*
* @return $environment
* The environment that was requested or FALSE if non-existent
*/
function apachesolr_environment_load($env_id) {
$environments = apachesolr_load_all_environments();
return isset($environments[$env_id]) ? $environments[$env_id] : FALSE;
}
/**
* Access callback for the delete page of an environment.
*
* @param $permission
* The permission that you allow access to
* @param $environment
* The environment you want to delete. Core environment cannot be deleted
*/
function apachesolr_environment_delete_page_access($permission, $environment) {
$is_default = $environment['env_id'] == apachesolr_default_environment();
return !$is_default && user_access($permission);
}
/**
* Function that deletes an environment
*
* @param $env_id
* The environment ID it needs to delete.
*
*/
function apachesolr_environment_delete($env_id) {
$environment = apachesolr_environment_load($env_id);
if ($environment) {
db_delete('apachesolr_environment')
->condition('env_id', $env_id)
->execute();
db_delete('apachesolr_environment_variable')
->condition('env_id', $env_id)
->execute();
db_delete('apachesolr_index_bundles')
->condition('env_id', $env_id)
->execute();
module_invoke_all('apachesolr_environment_delete', $environment);
apachesolr_environments_clear_cache();
}
}
/**
* Function that clones an environment
*
* @param $env_id
* The environment ID it needs to clone.
*
*/
function apachesolr_environment_clone($env_id) {
$environment = apachesolr_environment_load($env_id);
$environments = apachesolr_load_all_environments();
$environment['env_id'] = apachesolr_create_unique_id($environments, $env_id);
$environment['name'] = $environment['name'] . ' [cloned]';
apachesolr_environment_save($environment);
}
/**
* Generator for an unique ID of an environment
*
* @param $environments
* The environments that are available
* @param $original_environment
* The environment it needs to replicate an ID for.
*
* @return
* The new environment ID
*/
function apachesolr_create_unique_id($existing, $id) {
$count = 0;
$cloned_env_int = 0;
do {
$new_id = $id . '_' . $count;
$count++;
} while (isset($existing[$new_id]));
return $new_id;
}
/**
* Function that saves an environment
*
* @param $environment
* The environment it needs to save.
*
*/
function apachesolr_environment_save($environment) {
module_load_include('inc', 'apachesolr', 'apachesolr.index');
$default = array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => '');
// If the environment has been saved to the database before, we need to make
// sure we don't loose anything when saving it; therefore, load the existing
// environment and merge its' data with the new one.
$old_environment = apachesolr_environment_load($environment['env_id']);
if (!empty($old_environment['in_code_only']) && $environment != $old_environment) {
$environment = drupal_array_merge_deep($old_environment, $environment);
}
$conf = isset($environment['conf']) ? $environment['conf'] : array();
$index_bundles = isset($environment['index_bundles']) ? $environment['index_bundles'] : array();
// Remove any unexpected fields.
// @todo - get this from the schema?.
$environment = array_intersect_key($environment, $default);
db_merge('apachesolr_environment')
->key(array('env_id' => $environment['env_id']))
->fields($environment)
->execute();
// Update the environment variables (if any).
foreach ($conf as $name => $value) {
db_merge('apachesolr_environment_variable')
->key(array('env_id' => $environment['env_id'], 'name' => $name))
->fields(array('value' => serialize($value)))
->execute();
}
// Update the index bundles (if any).
foreach ($index_bundles as $entity_type => $bundles) {
apachesolr_index_set_bundles($environment['env_id'], $entity_type, $bundles);
}
apachesolr_environments_clear_cache();
}
/**
* Clear all caches for environments.
*/
function apachesolr_environments_clear_cache() {
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');
}
}
/**
* Get a named variable, or return the default.
*
* @see variable_get()
*/
function apachesolr_environment_variable_get($env_id, $name, $default = NULL) {
$environment = apachesolr_environment_load($env_id);
if (isset($environment['conf'][$name])) {
return $environment['conf'][$name];
}
return $default;
}
/**
* Set a named variable, or return the default.
*
* @see variable_set()
*/
function apachesolr_environment_variable_set($env_id, $name, $value) {
apachesolr_environment_save_to_database($env_id);
db_merge('apachesolr_environment_variable')
->key(array('env_id' => $env_id, 'name' => $name))
->fields(array('value' => serialize($value)))
->execute();
apachesolr_environments_clear_cache();
}
/**
* Get a named variable, or return the default.
*
* @see variable_del()
*/
function apachesolr_environment_variable_del($env_id, $name) {
apachesolr_environment_save_to_database($env_id);
db_delete('apachesolr_environment_variable')
->condition('env_id', $env_id)
->condition('name', $name)
->execute();
apachesolr_environments_clear_cache();
}
/**
* Makes sure that the given environment has been saved to the database.
*
* This is a required step before any environment-related data is modified or
* deleted. It ensures that ctools exportables can properly determine whether
* something has been overridden.
*
* @param string $env_id
* The environment ID.
*
* @see https://www.drupal.org/node/1439564#comment-8727467
*/
function apachesolr_environment_save_to_database($env_id) {
$environment = apachesolr_environment_load($env_id);
if (!empty($environment['in_code_only'])) {
apachesolr_environment_save($environment);
}
}
/**
* Checks if a specific Apache Solr server is available.
*
* @return boolean TRUE if the server can be pinged, FALSE otherwise.
*/
function apachesolr_server_status($url, $class = NULL) {
$status = &drupal_static(__FUNCTION__, array());
if (!interface_exists('DrupalApacheSolrServiceInterface')) {
require_once(dirname(__FILE__) . '/apachesolr.interface.inc');
}
if (empty($class)) {
$class = variable_get('apachesolr_service_class', 'DrupalApacheSolrService');
}
$key = $url . '|' . $class;
// Static store insures we don't ping the server more than once per page load.
if (!isset($status[$key])) {
$ping = FALSE;
try {
// Takes advantage of auto-loading.
// @Todo : Do we have to specify the env_id?
$solr = new $class($url);
$ping = @$solr->ping(variable_get('apachesolr_ping_timeout', 4));
}
catch (Exception $e) {
watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR);
}
$status[$key] = $ping;
}
return $status[$key];
}
/**
* Execute a keyword search based on a query object.
*
* Normally this function is used with the default (dismax) handler for keyword
* searches. The $final_query that's returned will have been modified by
* both hook_apachesolr_query_prepare() and hook_apachesolr_query_alter().
*
* @param $current_query
* A query object from apachesolr_drupal_query(). It will be modified by
* hook_apachesolr_query_prepare() and then cached in apachesolr_current_query().
* @param $page
* For paging into results, using $current_query->params['rows'] results per page.
*
* @return array($final_query, $response)
*
* @throws Exception
*/
function apachesolr_do_query(DrupalSolrQueryInterface $current_query) {
if (!is_object($current_query)) {
throw new Exception(t('NULL query object in function apachesolr_do_query()'));
}
// Allow modules to alter the query prior to statically caching it.
// This can e.g. be used to add available sorts.
$searcher = $current_query->getSearcher();
if (module_exists('facetapi')) {
// Gets enabled facets, adds filter queries to $params.
$adapter = facetapi_adapter_load($searcher);
if ($adapter) {
// Realm could be added but we want all the facets
$adapter->addActiveFilters($current_query);
}
$fq = $current_query->getParam('fq');
/*
//nd@oieau.fr : horrible fix in order to have "AND" operator for facet
//===========> in fact we have to fix it in solrconfig.xml file